Merge pull request #1108 from Bugagazavr/0.4-imp

Start working on specs implementation
This commit is contained in:
Brad Rydzewski 2015-07-26 16:29:08 -07:00
commit a400fc6957
54 changed files with 3236 additions and 64 deletions

6
Godeps/Godeps.json generated
View file

@ -2,7 +2,7 @@
"ImportPath": "github.com/drone/drone",
"GoVersion": "go1.4.2",
"Packages": [
"github.com/drone/drone/..."
"github.com/drone/drone..."
],
"Deps": [
{
@ -10,6 +10,10 @@
"Comment": "null-236",
"Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e"
},
{
"ImportPath": "github.com/Bugagazavr/go-gitlab-client",
"Rev": "912567bb7e65212c910733b3bfa178b11049a70e"
},
{
"ImportPath": "github.com/BurntSushi/migration",
"Rev": "c45b897f13350786ccaf2b7403b92b1c7ad85844"

View file

@ -0,0 +1,3 @@
.DS_Store
.idea
examples/config.json

View file

@ -0,0 +1,6 @@
language: go
go:
- 1.4
script:
- make deps test

View file

@ -0,0 +1,8 @@
all:deps test
deps:
go get github.com/stretchr/testify
go get ./...
test:
go test -cover -short ./...

View file

@ -0,0 +1,78 @@
go-gitlab-client
================
This is a fork of project https://github.com/plouc/go-gitlab-client
go-gitlab-client is a simple client written in golang to consume gitlab API.
[![Build Status](https://travis-ci.org/Bugagazavr/go-gitlab-client.svg?branch=master)](https://travis-ci.org/Bugagazavr/go-gitlab-client)
##features
*
###Session [gitlab api doc](http://doc.gitlab.com/ce/api/session.html)
* get session
*
###Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html)
* list projects
* get single project
*
###Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html)
* list repository branches
* get single repository branch
* list project repository tags
* list repository commits
* list project hooks
* add/get/edit/rm project hook
*
###Users [gitlab api doc](http://doc.gitlab.com/ce/api/users.html)
* get single user
* manage user keys
*
###Deploy Keys [gitlab api doc](http://doc.gitlab.com/ce/api/deploy_keys.html)
* list project deploy keys
* add/get/rm project deploy key
##Installation
To install go-gitlab-client, use `go get`:
go get github.com/bugagazavr/go-gitlab-client
Import the `go-gitlab-client` package into your code:
```go
package whatever
import (
"github.com/bugagazavr/go-gitlab-client"
)
```
##Update
To update `go-gitlab-client`, use `go get -u`:
go get -u github.com/bugagazavr/go-gitlab-client
##Documentation
Visit the docs at http://godoc.org/github.com/Bugagazavr/go-gitlab-client
## Examples
You can play with the examples located in the `examples` directory
* [projects](https://github.com/Bugagazavr/go-gitlab-client/tree/master/examples/projects)
* [repositories](https://github.com/Bugagazavr/go-gitlab-client/tree/master/examples/repositories)

View file

@ -0,0 +1,118 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
// ID
project_url_deploy_keys = "/projects/:id/keys" // Get list of project deploy keys
// PROJECT ID AND KEY ID
project_url_deploy_key = "/projects/:id/keys/:key_id" // Get single project deploy key
)
/*
Get list of project deploy keys.
GET /projects/:id/keys
Parameters:
id The ID of a project
*/
func (g *Gitlab) ProjectDeployKeys(id string) ([]*PublicKey, error) {
url, opaque := g.ResourceUrlRaw(project_url_deploy_keys, map[string]string{":id": id})
var deployKeys []*PublicKey
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &deployKeys)
}
return deployKeys, err
}
/*
Get single project deploy key.
GET /projects/:id/keys/:key_id
Parameters:
id The ID of a project
key_id The ID of a key
*/
func (g *Gitlab) ProjectDeployKey(id, key_id string) (*PublicKey, error) {
url, opaque := g.ResourceUrlRaw(project_url_deploy_key, map[string]string{
":id": id,
":key_id": key_id,
})
var deployKey *PublicKey
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &deployKey)
}
return deployKey, err
}
/*
Add deploy key to project.
POST /projects/:id/keys
Parameters:
id The ID of a project
title The key title
key The key value
*/
func (g *Gitlab) AddProjectDeployKey(id, title, key string) error {
var err error
path, opaque := g.ResourceUrlRaw(project_url_deploy_keys, map[string]string{":id": id})
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequestRaw("POST", path, opaque, []byte(body))
return err
}
/*
Remove deploy key from project
DELETE /projects/:id/keys/:key_id
Parameters:
id The ID of a project
key_id The ID of a key
*/
func (g *Gitlab) RemoveProjectDeployKey(id, key_id string) error {
url, opaque := g.ResourceUrlRaw(project_url_deploy_key, map[string]string{
":id": id,
":key_id": key_id,
})
var err error
_, err = g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}

View file

@ -0,0 +1,72 @@
package gogitlab
import (
"encoding/xml"
"fmt"
"time"
)
type Person struct {
Name string `xml:"name"json:"name"`
Email string `xml:"email"json:"email"`
}
type Link struct {
Rel string `xml:"rel,attr,omitempty"json:"rel"`
Href string `xml:"href,attr"json:"href"`
}
type ActivityFeed struct {
Title string `xml:"title"json:"title"`
Id string `xml:"id"json:"id"`
Link []Link `xml:"link"json:"link"`
Updated time.Time `xml:"updated,attr"json:"updated"`
Entries []*FeedCommit `xml:"entry"json:"entries"`
}
type FeedCommit struct {
Id string `xml:"id"json:"id"`
Title string `xml:"title"json:"title"`
Link []Link `xml:"link"json:"link"`
Updated time.Time `xml:"updated"json:"updated"`
Author Person `xml:"author"json:"author"`
Summary string `xml:"summary"json:"summary"`
//<media:thumbnail width="40" height="40" url="https://secure.gravatar.com/avatar/7070eab7c6206530d3b7820362227fec?s=40&amp;d=mm"/>
}
func (g *Gitlab) Activity() (ActivityFeed, error) {
url := g.BaseUrl + dasboard_feed_path + "?private_token=" + g.Token
fmt.Println(url)
contents, err := g.buildAndExecRequest("GET", url, nil)
if err != nil {
fmt.Println("%s", err)
}
var activity ActivityFeed
err = xml.Unmarshal(contents, &activity)
if err != nil {
fmt.Println("%s", err)
}
return activity, err
}
func (g *Gitlab) RepoActivityFeed(feedPath string) ActivityFeed {
url := g.BaseUrl + g.RepoFeedPath + "?private_token=" + g.Token
contents, err := g.buildAndExecRequest("GET", url, nil)
if err != nil {
fmt.Println("%s", err)
}
var activity ActivityFeed
err = xml.Unmarshal(contents, &activity)
if err != nil {
fmt.Println("%s", err)
}
return activity
}

View file

@ -0,0 +1,5 @@
{
"host": "https://gitlab.domain.com",
"api_path": "/api/v3",
"token": "TOKEN"
}

View file

@ -0,0 +1,195 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/bugagazavr/go-gitlab-client"
"io/ioutil"
"os"
"strconv"
"time"
)
type Config struct {
Host string `json:"host"`
ApiPath string `json:"api_path"`
Token string `json:"token"`
}
func main() {
help := flag.Bool("help", false, "Show usage")
file, e := ioutil.ReadFile("../config.json")
if e != nil {
fmt.Printf("Config file error: %v\n", e)
os.Exit(1)
}
var config Config
json.Unmarshal(file, &config)
fmt.Printf("Results: %+v\n", config)
var gitlab *gogitlab.Gitlab
gitlab = gogitlab.NewGitlab(config.Host, config.ApiPath, config.Token)
var method string
flag.StringVar(&method, "m", "", "Specify method to retrieve projects infos, available methods:\n"+
" > -m projects\n"+
" > -m project -id PROJECT_ID\n"+
" > -m hooks -id PROJECT_ID\n"+
" > -m branches -id PROJECT_ID\n"+
" > -m merge_requests -id PROJECT_ID\n"+
" > -m team -id PROJECT_ID")
var id string
flag.StringVar(&id, "id", "", "Specify repository id")
flag.Usage = func() {
fmt.Printf("Usage:\n")
flag.PrintDefaults()
}
flag.Parse()
if *help == true || method == "" {
flag.Usage()
return
}
startedAt := time.Now()
defer func() {
fmt.Printf("processed in %v\n", time.Now().Sub(startedAt))
}()
switch method {
case "projects":
fmt.Println("Fetching projects…")
projects, err := gitlab.Projects(1, 100)
if err != nil {
fmt.Println(err.Error())
return
}
for _, project := range projects {
fmt.Printf("> %6d | %s\n", project.Id, project.Name)
}
case "project":
fmt.Println("Fetching project…")
if id == "" {
flag.Usage()
return
}
project, err := gitlab.Project(id)
if err != nil {
fmt.Println(err.Error())
return
}
format := "> %-23s: %s\n"
fmt.Printf("%s\n", project.Name)
fmt.Printf(format, "id", strconv.Itoa(project.Id))
fmt.Printf(format, "name", project.Name)
fmt.Printf(format, "description", project.Description)
fmt.Printf(format, "default branch", project.DefaultBranch)
if project.Owner != nil {
fmt.Printf(format, "owner.name", project.Owner.Username)
}
fmt.Printf(format, "public", strconv.FormatBool(project.Public))
fmt.Printf(format, "path", project.Path)
fmt.Printf(format, "path with namespace", project.PathWithNamespace)
fmt.Printf(format, "issues enabled", strconv.FormatBool(project.IssuesEnabled))
fmt.Printf(format, "merge requests enabled", strconv.FormatBool(project.MergeRequestsEnabled))
fmt.Printf(format, "wall enabled", strconv.FormatBool(project.WallEnabled))
fmt.Printf(format, "wiki enabled", strconv.FormatBool(project.WikiEnabled))
fmt.Printf(format, "created at", project.CreatedAtRaw)
//fmt.Printf(format, "namespace", project.Namespace)
case "branches":
fmt.Println("Fetching project branches…")
if id == "" {
flag.Usage()
return
}
branches, err := gitlab.ProjectBranches(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, branch := range branches {
fmt.Printf("> %s\n", branch.Name)
}
case "merge_requests":
fmt.Println("Fetching project merge_requests…")
if id == "" {
flag.Usage()
return
}
mrs, err := gitlab.ProjectMergeRequests(id, 0, 30, "opened")
if err != nil {
fmt.Println(err.Error())
return
}
for _, mr := range mrs {
author := ""
if mr.Author != nil {
author = mr.Author.Username
}
assignee := ""
if mr.Assignee != nil {
assignee = mr.Assignee.Username
}
fmt.Printf(" %s -> %s [%s] author[%s] assignee[%s]\n",
mr.SourceBranch, mr.TargetBranch, mr.State,
author, assignee)
}
case "hooks":
fmt.Println("Fetching project hooks…")
if id == "" {
flag.Usage()
return
}
hooks, err := gitlab.ProjectHooks(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, hook := range hooks {
fmt.Printf("> [%d] %s, created on %s\n", hook.Id, hook.Url, hook.CreatedAtRaw)
}
case "team":
fmt.Println("Fetching project team members…")
if id == "" {
flag.Usage()
return
}
members, err := gitlab.ProjectMembers(id)
if err != nil {
fmt.Println(err.Error())
return
}
for _, member := range members {
fmt.Printf("> [%d] %s (%s) since %s\n", member.Id, member.Username, member.Name, member.CreatedAt)
}
}
}

View file

@ -0,0 +1,96 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/bugagazavr/go-gitlab-client"
"io/ioutil"
"os"
"time"
)
type Config struct {
Host string `json:"host"`
ApiPath string `json:"api_path"`
Token string `json:"token"`
}
func main() {
help := flag.Bool("help", false, "Show usage")
file, e := ioutil.ReadFile("../config.json")
if e != nil {
fmt.Printf("Config file error: %v\n", e)
os.Exit(1)
}
var config Config
json.Unmarshal(file, &config)
fmt.Printf("Results: %+v\n", config)
gitlab := gogitlab.NewGitlab(config.Host, config.ApiPath, config.Token)
var method string
flag.StringVar(&method, "m", "", "Specify method to retrieve repositories, available methods:\n"+
" > branches\n"+
" > branch\n"+
" > tags\n"+
" > commits")
var id string
flag.StringVar(&id, "id", "", "Specify repository id")
flag.Usage = func() {
fmt.Printf("Usage:\n")
flag.PrintDefaults()
}
flag.Parse()
if *help == true || method == "" || id == "" {
flag.Usage()
return
}
startedAt := time.Now()
defer func() {
fmt.Printf("processed in %v\n", time.Now().Sub(startedAt))
}()
switch method {
case "branches":
fmt.Println("Fetching repository branches…")
branches, err := gitlab.RepoBranches(id)
if err != nil {
fmt.Println(err.Error())
}
for _, branch := range branches {
fmt.Printf("> %s\n", branch.Name)
}
case "branch":
case "tags":
fmt.Println("Fetching repository tags…")
tags, err := gitlab.RepoTags(id)
if err != nil {
fmt.Println(err.Error())
}
for _, tag := range tags {
fmt.Printf("> %s\n", tag.Name)
}
case "commits":
fmt.Println("Fetching repository commits…")
commits, err := gitlab.RepoCommits(id)
if err != nil {
fmt.Println(err.Error())
}
for _, commit := range commits {
fmt.Printf("%s > [%s] %s\n", commit.CreatedAt.Format("Mon 02 Jan 15:04"), commit.Author_Name, commit.Title)
}
}
}

View file

@ -0,0 +1,191 @@
// Package github implements a simple client to consume gitlab API.
package gogitlab
import (
"bytes"
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const (
dasboard_feed_path = "/dashboard.atom"
)
type Gitlab struct {
BaseUrl string
ApiPath string
RepoFeedPath string
Token string
Bearer bool
Client *http.Client
}
const (
dateLayout = "2006-01-02T15:04:05-07:00"
)
var (
skipCertVerify = flag.Bool("gitlab.skip-cert-check", false,
`If set to true, gitlab client will skip certificate checking for https, possibly exposing your system to MITM attack.`)
)
func NewGitlab(baseUrl, apiPath, token string) *Gitlab {
return NewGitlabCert(baseUrl, apiPath, token, *skipCertVerify)
}
func NewGitlabCert(baseUrl, apiPath, token string, skipVerify bool) *Gitlab {
config := &tls.Config{InsecureSkipVerify: skipVerify}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: config,
}
client := &http.Client{Transport: tr}
return &Gitlab{
BaseUrl: baseUrl,
ApiPath: apiPath,
Token: token,
Client: client,
}
}
func (g *Gitlab) ResourceUrl(url string, params map[string]string) string {
if params != nil {
for key, val := range params {
url = strings.Replace(url, key, encodeParameter(val), -1)
}
}
url = g.BaseUrl + g.ApiPath + url
if !g.Bearer {
url = url + "?private_token=" + g.Token
}
return url
}
func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, error) {
var req *http.Request
var err error
if body != nil {
reader := bytes.NewReader(body)
req, err = http.NewRequest(method, url, reader)
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
panic("Error while building gitlab request")
}
if g.Bearer {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.Token))
}
resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)
}
if resp.StatusCode >= 400 {
err = fmt.Errorf("*Gitlab.buildAndExecRequest failed: <%d> %s", resp.StatusCode, req.URL)
}
return contents, err
}
func (g *Gitlab) ResourceUrlQuery(u string, params, query map[string]string) string {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
query_params := url.Values{}
if !g.Bearer {
query_params.Add("private_token", g.Token)
}
if query != nil {
for key, val := range query {
query_params.Set(key, val)
}
}
u = g.BaseUrl + g.ApiPath + u + "?" + query_params.Encode()
return u
}
func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, string) {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
path := u
u = g.BaseUrl + g.ApiPath + path
if !g.Bearer {
u = u + "?private_token=" + g.Token
}
p, err := url.Parse(u)
if err != nil {
return u, ""
}
opaque := "//" + p.Host + p.Path
return u, opaque
}
func (g *Gitlab) buildAndExecRequestRaw(method, url, opaque string, body []byte) ([]byte, error) {
var req *http.Request
var err error
if body != nil {
reader := bytes.NewReader(body)
req, err = http.NewRequest(method, url, reader)
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
panic("Error while building gitlab request")
}
if g.Bearer {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.Token))
}
if len(opaque) > 0 {
req.URL.Opaque = opaque
}
resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)
}
if resp.StatusCode >= 400 {
err = fmt.Errorf("*Gitlab.buildAndExecRequestRaw failed: <%d> %s", resp.StatusCode, req.URL)
}
return contents, err
}

View file

@ -0,0 +1,13 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestResourceUrl(t *testing.T) {
gitlab := NewGitlab("http://base_url/", "api_path", "token")
assert.Equal(t, gitlab.ResourceUrl(projects_url, nil), "http://base_url/api_path/projects?private_token=token")
assert.Equal(t, gitlab.ResourceUrl(project_url, map[string]string{":id": "123"}), "http://base_url/api_path/projects/123?private_token=token")
}

View file

@ -0,0 +1,16 @@
package gogitlab
import (
"io/ioutil"
"net/http"
"net/http/httptest"
)
func Stub(filename string) (*httptest.Server, *Gitlab) {
stub, _ := ioutil.ReadFile(filename)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(stub))
}))
gitlab := NewGitlab(ts.URL, "", "")
return ts, gitlab
}

View file

@ -0,0 +1,154 @@
package gogitlab
import (
"encoding/json"
"fmt"
"strings"
)
type HookObjAttr struct {
Id int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
AssigneeId int `json:"assignee_id,omitempty"`
AuthorId int `json:"author_id,omitempty"`
ProjectId int `json:"project_id,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
Position int `json:"position,omitempty"`
BranchName string `json:"branch_name,omitempty"`
Description string `json:"description,omitempty"`
MilestoneId int `json:"milestone_id,omitempty"`
State string `json:"state,omitempty"`
IId int `json:"iid,omitempty"`
TargetBranch string `json:"target_branch,omitempty"`
SourceBranch string `json:"source_branch,omitempty"`
SourceProjectId int `json:"source_project_id,omitempty"`
StCommits string `json:"st_commits,omitempty"`
StDiffs string `json:"st_diffs,omitempty"`
MergeStatus string `json:"merge_status,omitempty"`
TargetProjectId int `json:"target_project_id,omitempty"`
Source *hProject `json:"source,omitempty"`
Target *hProject `json:"target,omitempty"`
LastCommit *hCommit `json:"last_commit,omitempty"`
}
type hProject struct {
Name string `json:"name"`
SshUrl string `json:"ssh_url"`
HttpUrl string `json:"http_url"`
VisibilityLevel int `json:"visibility_level"`
Namespace string `json:"namespace"`
}
type hRepository struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Description string `json:"description,omitempty"`
Homepage string `json:"homepage,omitempty"`
GitHttpUrl string `json:"git_http_url,omitempty"`
GitSshUrl string `json:"git_ssh_url,omitempty"`
VisibilityLevel int `json:"visibility_level,omitempty"`
}
type hCommit struct {
Id string `json:"id,omitempty"`
Message string `json:"message,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
URL string `json:"url,omitempty"`
Author *Person `json:"author,omitempty"`
}
type HookPayload struct {
Before string `json:"before,omitempty"`
After string `json:"after,omitempty"`
Ref string `json:"ref,omitempty"`
UserId int `json:"user_id,omitempty"`
UserName string `json:"user_name,omitempty"`
ProjectId int `json:"project_id,omitempty"`
Repository *hRepository `json:"repository,omitempty"`
Commits []hCommit `json:"commits,omitempty"`
TotalCommitsCount int `json:"total_commits_count,omitempty"`
ObjectKind string `json:"object_kind,omitempty"`
ObjectAttributes *HookObjAttr `json:"object_attributes,omitempty"`
}
// ParseHook parses hook payload from GitLab
func ParseHook(payload []byte) (*HookPayload, error) {
hp := HookPayload{}
if err := json.Unmarshal(payload, &hp); err != nil {
return nil, err
}
// Basic sanity check
switch {
case len(hp.ObjectKind) == 0:
// Assume this is a post-receive within repository
if len(hp.After) == 0 {
return nil, fmt.Errorf("Invalid hook received, commit hash not found.")
}
case hp.ObjectKind == "push":
if hp.Repository == nil {
return nil, fmt.Errorf("Invalid push hook received, attributes not found")
}
case hp.ObjectKind == "tag_push":
if hp.Repository == nil {
return nil, fmt.Errorf("Invalid tag push hook received, attributes not found")
}
case hp.ObjectKind == "issue":
fallthrough
case hp.ObjectKind == "merge_request":
if hp.ObjectAttributes == nil {
return nil, fmt.Errorf("Invalid hook received, attributes not found.")
}
default:
return nil, fmt.Errorf("Invalid hook received, payload format not recognized.")
}
return &hp, nil
}
// Type return current event type
// This function returns "unknown" type if event not supported
func (h *HookPayload) Type() string {
switch {
case strings.HasPrefix(h.Ref, "refs/heads/") && len(h.After) == 0:
return "branch_deleted"
case strings.HasPrefix(h.Ref, "refs/heads/") && len(h.Before) == 0:
return "branch"
case strings.HasPrefix(h.Ref, "refs/heads/"):
return "commit"
case strings.HasPrefix(h.Ref, "refs/tags/") && len(h.After) == 0:
return "tag_deleted"
case strings.HasPrefix(h.Ref, "refs/tags/"):
return "tag"
case h.ObjectKind == "issue":
return "issue"
case h.ObjectKind == "merge_request":
return "merge_request"
default:
return "unknown"
}
}
// Tag returns current tag for push event hook payload
// This function returns empty string for any other events
func (h *HookPayload) Tag() string {
return strings.TrimPrefix(h.Ref, "refs/tags/")
}
// Branch returns current branch for push event hook payload
// This function returns empty string for any other events
func (h *HookPayload) Branch() string {
return strings.TrimPrefix(h.Ref, "refs/heads/")
}
// Head returns the latest changeset for push event hook payload
func (h *HookPayload) Head() hCommit {
c := hCommit{}
for _, cm := range h.Commits {
if h.After == cm.Id {
return cm
}
}
return c
}

View file

@ -0,0 +1,45 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"io/ioutil"
"testing"
)
func TestParsePushHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/push.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "push")
assert.IsType(t, new(HookPayload), p)
assert.Equal(t, p.After, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
assert.Equal(t, p.Repository.URL, "git@example.com:mike/diasporadiaspora.git")
assert.Equal(t, p.Repository.GitHttpUrl, "http://example.com/mike/diaspora.git")
assert.Equal(t, p.Repository.GitSshUrl, "git@example.com:mike/diaspora.git")
assert.Equal(t, p.Repository.VisibilityLevel, 0)
assert.Equal(t, len(p.Commits), 2)
assert.Equal(t, p.Commits[0].Author.Email, "jordi@softcatala.org")
assert.Equal(t, p.Commits[1].Id, "da1560886d4f094c3e6c9ef40349f7d38b5d27d7")
assert.Equal(t, p.Branch(), "master")
assert.Equal(t, p.Head().Message, "fixed readme")
}
func TestParseIssueHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/issue.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "issue")
assert.Equal(t, p.ObjectAttributes.Id, 301)
}
func TestParseMergeRequestHook(t *testing.T) {
stub, _ := ioutil.ReadFile("stubs/hook_payloads/merge_request.json")
p, err := ParseHook([]byte(stub))
assert.Equal(t, err, nil)
assert.Equal(t, p.ObjectKind, "merge_request")
assert.Equal(t, p.ObjectAttributes.TargetBranch, "master")
assert.Equal(t, p.ObjectAttributes.SourceProjectId, p.ObjectAttributes.TargetProjectId)
}

View file

@ -0,0 +1,183 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
project_url_hooks = "/projects/:id/hooks" // Get list of project hooks
project_url_hook = "/projects/:id/hooks/:hook_id" // Get single project hook
)
type Hook struct {
Id int `json:"id,omitempty"`
Url string `json:"url,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
}
/*
Get list of project hooks.
GET /projects/:id/hooks
Parameters:
id The ID of a project
*/
func (g *Gitlab) ProjectHooks(id string) ([]*Hook, error) {
url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id})
var err error
var hooks []*Hook
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err != nil {
return hooks, err
}
err = json.Unmarshal(contents, &hooks)
return hooks, err
}
/*
Get single project hook.
GET /projects/:id/hooks/:hook_id
Parameters:
id The ID of a project
hook_id The ID of a hook
*/
func (g *Gitlab) ProjectHook(id, hook_id string) (*Hook, error) {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
hook := new(Hook)
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err != nil {
return hook, err
}
err = json.Unmarshal(contents, &hook)
return hook, err
}
/*
Add new project hook.
POST /projects/:id/hooks
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_url The hook URL
push_events Trigger hook on push events
issues_events Trigger hook on issues events
merge_requests_events Trigger hook on merge_requests events
*/
func (g *Gitlab) AddProjectHook(id, hook_url string, push_events, issues_events, merge_requests_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hooks, map[string]string{":id": id})
var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events)
_, err = g.buildAndExecRequestRaw("POST", url, opaque, []byte(body))
return err
}
/*
Edit existing project hook.
PUT /projects/:id/hooks/:hook_id
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_id The ID of a project hook
hook_url The hook URL
push_events Trigger hook on push events
issues_events Trigger hook on issues events
merge_requests_events Trigger hook on merge_requests events
*/
func (g *Gitlab) EditProjectHook(id, hook_id, hook_url string, push_events, issues_events, merge_requests_events bool) error {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
body := buildHookQuery(hook_url, push_events, issues_events, merge_requests_events)
_, err = g.buildAndExecRequestRaw("PUT", url, opaque, []byte(body))
return err
}
/*
Remove hook from project.
DELETE /projects/:id/hooks/:hook_id
Parameters:
id The ID or NAMESPACE/PROJECT_NAME of a project
hook_id The ID of hook to delete
*/
func (g *Gitlab) RemoveProjectHook(id, hook_id string) error {
url, opaque := g.ResourceUrlRaw(project_url_hook, map[string]string{
":id": id,
":hook_id": hook_id,
})
var err error
_, err = g.buildAndExecRequestRaw("DELETE", url, opaque, nil)
return err
}
/*
Build HTTP query to add or edit hook
*/
func buildHookQuery(hook_url string, push_events, issues_events, merge_requests_events bool) string {
v := url.Values{}
v.Set("url", hook_url)
if push_events {
v.Set("push_events", "true")
} else {
v.Set("push_events", "false")
}
if issues_events {
v.Set("issues_events", "true")
} else {
v.Set("issues_events", "false")
}
if merge_requests_events {
v.Set("merge_requests_events", "true")
} else {
v.Set("merge_requests_events", "false")
}
return v.Encode()
}

View file

@ -0,0 +1,16 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHook(t *testing.T) {
ts, gitlab := Stub("stubs/hooks/show.json")
hook, err := gitlab.ProjectHook("1", "2")
assert.Equal(t, err, nil)
assert.IsType(t, new(Hook), hook)
assert.Equal(t, hook.Url, "http://example.com/hook")
defer ts.Close()
}

View file

@ -0,0 +1,245 @@
package gogitlab
import (
"encoding/json"
"strconv"
"strings"
)
const (
projects_url = "/projects" // Get a list of projects owned by the authenticated user
projects_search_url = "/projects/search/:query" // Search for projects by name
project_url = "/projects/:id" // Get a specific project, identified by project ID or NAME
project_url_events = "/projects/:id/events" // Get project events
project_url_branches = "/projects/:id/repository/branches" // Lists all branches of a project
project_url_members = "/projects/:id/members" // List project team members
project_url_member = "/projects/:id/members/:user_id" // Get project team member
project_url_merge_requests = "/projects/:id/merge_requests" // List all merge requests of a project
)
type Member struct {
Id int
Username string
Email string
Name string
State string
CreatedAt string `json:"created_at,omitempty"`
// AccessLevel int
}
type Namespace struct {
Id int
Name string
Path string
Description string
Owner_Id int
Created_At string
Updated_At string
}
type ProjectAccess struct {
AccessLevel int `json:"access_level,omitempty"`
NotificationLevel int `json:"notification_level,omitempty"`
}
type GroupAccess struct {
AccessLevel int `json:"access_level,omitempty"`
NotificationLevel int `json:"notification_level,omitempty"`
}
type Permissions struct {
ProjectAccess *ProjectAccess `json:"project_access,omitempty"`
GroupAccess *GroupAccess `json:"group_access,omitempty"`
}
// A gitlab project
type Project struct {
Id int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
Owner *Member `json:"owner,omitempty"`
Public bool `json:"public,omitempty"`
Path string `json:"path,omitempty"`
PathWithNamespace string `json:"path_with_namespace,omitempty"`
IssuesEnabled bool `json:"issues_enabled,omitempty"`
MergeRequestsEnabled bool `json:"merge_requests_enabled,omitempty"`
WallEnabled bool `json:"wall_enabled,omitempty"`
WikiEnabled bool `json:"wiki_enabled,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
Namespace *Namespace `json:"namespace,omitempty"`
SshRepoUrl string `json:"ssh_url_to_repo"`
HttpRepoUrl string `json:"http_url_to_repo"`
Url string `json:"web_url"`
Permissions *Permissions `json:"permissions,omitempty"`
}
type MergeRequest struct {
Id int `json:"id,omitempty"`
// IId
TargetBranch string `json:"target_branch,omitempty"`
SourceBranch string `json:"source_branch,omitempty"`
ProjectId int `json:"project_id,omitempty"`
Title string `json:"title,omitempty"`
State string `json:"state,omitempty"`
Upvotes int `json:"upvotes,omitempty"`
Downvotes int `json:"downvotes,omitempty"`
Author *Member `json:"author,omitempty"`
Assignee *Member `json:"assignee,omitempty"`
Description string `json:"description,omitempty"`
}
/*
Get a list of all projects owned by the authenticated user.
*/
func (g *Gitlab) AllProjects() ([]*Project, error) {
var per_page = 100
var projects []*Project
for i := 1; true; i++ {
contents, err := g.Projects(i, per_page)
if err != nil {
return projects, err
}
for _, value := range contents {
projects = append(projects, value)
}
if len(projects) == 0 {
break
}
if len(projects)/i < per_page {
break
}
}
return projects, nil
}
/*
Get a list of projects owned by the authenticated user.
*/
func (g *Gitlab) Projects(page int, per_page int) ([]*Project, error) {
url := g.ResourceUrlQuery(projects_url, nil, map[string]string{"page": strconv.Itoa(page), "per_page": strconv.Itoa(per_page)})
var projects []*Project
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &projects)
}
return projects, err
}
/*
Get a specific project, identified by project ID or NAME,
which is owned by the authentication user.
Namespaced project may be retrieved by specifying the namespace
and its project name like this:
`namespace%2Fproject-name`
*/
func (g *Gitlab) Project(id string) (*Project, error) {
url, opaque := g.ResourceUrlRaw(project_url, map[string]string{":id": id})
var project *Project
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &project)
}
return project, err
}
/*
Lists all branches of a project.
*/
func (g *Gitlab) ProjectBranches(id string) ([]*Branch, error) {
url, opaque := g.ResourceUrlRaw(project_url_branches, map[string]string{":id": id})
var branches []*Branch
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branches)
}
return branches, err
}
func (g *Gitlab) ProjectMembers(id string) ([]*Member, error) {
url, opaque := g.ResourceUrlRaw(project_url_members, map[string]string{":id": id})
var members []*Member
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &members)
}
return members, err
}
/*
Lists all merge requests of a project.
*/
func (g *Gitlab) ProjectMergeRequests(id string, page int, per_page int, state string) ([]*MergeRequest, error) {
par := map[string]string{":id": id}
qry := map[string]string{
"state": state,
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(project_url_merge_requests, par, qry)
var mr []*MergeRequest
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &mr)
}
return mr, err
}
/*
Get single project id.
GET /projects/search/:query
Parameters:
namespace The namespace of a project
name The id of a project
*/
func (g *Gitlab) SearchProjectId(namespace string, name string) (id int, err error) {
url, opaque := g.ResourceUrlRaw(projects_search_url, map[string]string{
":query": strings.ToLower(name),
})
var projects []*Project
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &projects)
} else {
return id, err
}
for _, project := range projects {
if project.Namespace.Name == namespace {
id = project.Id
}
}
return id, err
}

View file

@ -0,0 +1,70 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestALlProjects(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
projects, err := gitlab.AllProjects()
assert.Equal(t, err, nil)
assert.Equal(t, len(projects), 2)
defer ts.Close()
}
func TestProjects(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
projects, err := gitlab.Projects(1, 100)
assert.Equal(t, err, nil)
assert.Equal(t, len(projects), 2)
defer ts.Close()
}
func TestProject(t *testing.T) {
ts, gitlab := Stub("stubs/projects/show.json")
project, err := gitlab.Project("1")
assert.Equal(t, err, nil)
assert.IsType(t, new(Project), project)
assert.Equal(t, project.SshRepoUrl, "git@example.com:diaspora/diaspora-project-site.git")
assert.Equal(t, project.HttpRepoUrl, "http://example.com/diaspora/diaspora-project-site.git")
defer ts.Close()
}
func TestProjectBranches(t *testing.T) {
ts, gitlab := Stub("stubs/projects/branches/index.json")
branches, err := gitlab.ProjectBranches("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(branches), 2)
defer ts.Close()
}
func TestProjectMergeRequests(t *testing.T) {
ts, gitlab := Stub("stubs/projects/merge_requests/index.json")
defer ts.Close()
mr, err := gitlab.ProjectMergeRequests("1", 0, 30, "all")
assert.Equal(t, err, nil)
assert.Equal(t, len(mr), 1)
if len(mr) > 0 {
assert.Equal(t, mr[0].TargetBranch, "master")
assert.Equal(t, mr[0].SourceBranch, "test1")
}
}
func TestSearchProjectId(t *testing.T) {
ts, gitlab := Stub("stubs/projects/index.json")
namespace := "Brightbox"
name := "Puppet"
id, err := gitlab.SearchProjectId(namespace, name)
assert.Equal(t, err, nil)
assert.Equal(t, id, 6)
defer ts.Close()
}

View file

@ -0,0 +1,69 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
// ID
user_keys = "/user/keys" // Get current user keys
user_key = "/user/keys/:id" // Get user key by id
custom_user_keys = "/user/:id/keys" // Create key for user with :id
)
type PublicKey struct {
Id int `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Key string `json:"key,omitempty"`
CreatedAtRaw string `json:"created_at,omitempty"`
}
func (g *Gitlab) UserKeys() ([]*PublicKey, error) {
url := g.ResourceUrl(user_keys, nil)
var keys []*PublicKey
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &keys)
}
return keys, err
}
func (g *Gitlab) UserKey(id string) (*PublicKey, error) {
url := g.ResourceUrl(user_key, map[string]string{":id": id})
var key *PublicKey
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &key)
}
return key, err
}
func (g *Gitlab) AddKey(title, key string) error {
path := g.ResourceUrl(user_keys, nil)
var err error
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequest("POST", path, []byte(body))
return err
}
func (g *Gitlab) AddUserKey(id, title, key string) error {
path := g.ResourceUrl(user_keys, map[string]string{":id": id})
var err error
v := url.Values{}
v.Set("title", title)
v.Set("key", key)
body := v.Encode()
_, err = g.buildAndExecRequest("POST", path, []byte(body))
return err
}
func (g *Gitlab) DeleteKey(id string) error {
url := g.ResourceUrl(user_key, map[string]string{":id": id})
var err error
_, err = g.buildAndExecRequest("DELETE", url, nil)
return err
}

View file

@ -0,0 +1,50 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetUserKeys(t *testing.T) {
ts, gitlab := Stub("stubs/public_keys/index.json")
keys, err := gitlab.UserKeys()
assert.Equal(t, err, nil)
assert.Equal(t, len(keys), 2)
defer ts.Close()
}
func TestGetUserKey(t *testing.T) {
ts, gitlab := Stub("stubs/public_keys/show.json")
key, err := gitlab.UserKey("1")
assert.Equal(t, err, nil)
assert.IsType(t, new(PublicKey), key)
assert.Equal(t, key.Title, "Public key")
assert.Equal(t, key.Key, "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=")
defer ts.Close()
}
func TestAddKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.AddKey("Public key", "stubbed key")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestAddUserKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.AddUserKey("1", "Public key", "stubbed key")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestDeleteKey(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.DeleteKey("1")
assert.Equal(t, err, nil)
defer ts.Close()
}

View file

@ -0,0 +1,246 @@
package gogitlab
import (
"encoding/json"
"net/url"
"time"
)
const (
repo_url_branches = "/projects/:id/repository/branches" // List repository branches
repo_url_branch = "/projects/:id/repository/branches/:branch" // Get a specific branch of a project.
repo_url_tags = "/projects/:id/repository/tags" // List project repository tags
repo_url_commits = "/projects/:id/repository/commits" // List repository commits
repo_url_tree = "/projects/:id/repository/tree" // List repository tree
repo_url_raw_file = "/projects/:id/repository/blobs/:sha" // Get raw file content for specific commit/branch
)
type BranchCommit struct {
Id string `json:"id,omitempty"`
Tree string `json:"tree,omitempty"`
AuthoredDateRaw string `json:"authored_date,omitempty"`
CommittedDateRaw string `json:"committed_date,omitempty"`
Message string `json:"message,omitempty"`
Author *Person `json:"author,omitempty"`
Committer *Person `json:"committer,omitempty"`
/*
"parents": [
{"id": "9b0c4b08e7890337fc8111e66f809c8bbec467a9"},
{"id": "3ac634dca850cab70ab14b43ad6073d1e0a7827f"}
]
*/
}
type Branch struct {
Name string `json:"name,omitempty"`
Protected bool `json:"protected,omitempty"`
Commit *BranchCommit `json:"commit,omitempty"`
}
type Tag struct {
Name string `json:"name,omitempty"`
Protected bool `json:"protected,omitempty"`
Commit *BranchCommit `json:"commit,omitempty"`
}
type Commit struct {
Id string
Short_Id string
Title string
Author_Name string
Author_Email string
Created_At string
CreatedAt time.Time
}
type File struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Mode string `json:"mode,omitempty"`
Children []*File
}
/*
Get a list of repository branches from a project, sorted by name alphabetically.
GET /projects/:id/repository/branches
Parameters:
id The ID of a project
Usage:
branches, err := gitlab.RepoBranches("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, branch := range branches {
fmt.Printf("%+v\n", branch)
}
*/
func (g *Gitlab) RepoBranches(id string) ([]*Branch, error) {
url, opaque := g.ResourceUrlRaw(repo_url_branches, map[string]string{":id": id})
var branches []*Branch
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branches)
}
return branches, err
}
/*
Get a single project repository branch.
GET /projects/:id/repository/branches/:branch
Parameters:
id The ID of a project
branch The name of the branch
*/
func (g *Gitlab) RepoBranch(id, refName string) (*Branch, error) {
url, opaque := g.ResourceUrlRaw(repo_url_branch, map[string]string{
":id": id,
":branch": refName,
})
branch := new(Branch)
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &branch)
}
return branch, err
}
/*
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
GET /projects/:id/repository/tags
Parameters:
id The ID of a project
Usage:
tags, err := gitlab.RepoTags("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, tag := range tags {
fmt.Printf("%+v\n", tag)
}
*/
func (g *Gitlab) RepoTags(id string) ([]*Tag, error) {
url, opaque := g.ResourceUrlRaw(repo_url_tags, map[string]string{":id": id})
var tags []*Tag
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &tags)
}
return tags, err
}
/*
Get a list of repository commits in a project.
GET /projects/:id/repository/commits
Parameters:
id The ID of a project
refName The name of a repository branch or tag or if not given the default branch
Usage:
commits, err := gitlab.RepoCommits("your_projet_id")
if err != nil {
fmt.Println(err.Error())
}
for _, commit := range commits {
fmt.Printf("%+v\n", commit)
}
*/
func (g *Gitlab) RepoCommits(id string) ([]*Commit, error) {
url, opaque := g.ResourceUrlRaw(repo_url_commits, map[string]string{":id": id})
var commits []*Commit
contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &commits)
if err == nil {
for _, commit := range commits {
t, _ := time.Parse(dateLayout, commit.Created_At)
commit.CreatedAt = t
}
}
}
return commits, err
}
/*
Get Raw file content
*/
func (g *Gitlab) RepoRawFile(id, sha, filepath string) ([]byte, error) {
url_ := g.ResourceUrlQuery(repo_url_raw_file, map[string]string{
":id": id,
":sha": sha,
}, map[string]string{
"filepath": filepath,
})
p, err := url.Parse(url_)
if err != nil {
return nil, err
}
opaque := "//" + p.Host + p.Path
contents, err := g.buildAndExecRequestRaw("GET", url_, opaque, nil)
return contents, err
}
/*
Get Raw file content
*/
func (g *Gitlab) RepoTree(id, ref, path string) ([]*File, error) {
url := g.ResourceUrlQuery(repo_url_tree, map[string]string{
":id": id,
}, map[string]string{
"ref": ref,
"path": path,
})
var files []*File
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &files)
}
for _, f := range files {
if f.Type == "tree" {
f.Children, err = g.RepoTree(id, ref, path+"/"+f.Name)
}
}
return files, err
}

View file

@ -0,0 +1,43 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestRepoBranches(t *testing.T) {
ts, gitlab := Stub("stubs/branches/index.json")
branches, err := gitlab.RepoBranches("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(branches), 1)
defer ts.Close()
}
func TestRepoBranch(t *testing.T) {
ts, gitlab := Stub("stubs/branches/show.json")
branch, err := gitlab.RepoBranch("1", "master")
assert.Equal(t, err, nil)
assert.IsType(t, new(Branch), branch)
assert.Equal(t, branch.Name, "master")
defer ts.Close()
}
func TestRepoTags(t *testing.T) {
ts, gitlab := Stub("stubs/tags/index.json")
tags, err := gitlab.RepoTags("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(tags), 1)
defer ts.Close()
}
func TestRepoCommits(t *testing.T) {
ts, gitlab := Stub("stubs/commits/index.json")
commits, err := gitlab.RepoCommits("1")
assert.Equal(t, err, nil)
assert.Equal(t, len(commits), 2)
defer ts.Close()
}

View file

@ -0,0 +1,54 @@
package gogitlab
import (
"encoding/json"
"net/url"
)
const (
session_path = "/session"
)
type Session struct {
Id int `json:"id"`
UserName string `json:"username"`
Name string `json:"name"`
Blocked bool `json:"blocked"`
State string `json:"state"`
AvatarURL string `json:"avatar_url",omitempty`
IsAdmin bool `json:"is_admin"`
Bio string `json:"bio",omitempty`
Email string `json:"email"`
ThemeId int `json:"theme_id",omitempty`
ColorSchemeId int `json:"color_scheme_id",omitempty`
ExternUid string `json:"extern_uid",omitempty`
Provider string `json:"provider",omitempty`
CanCreateGroup bool `json:"can_create_group"`
CanCreateProject bool `json:"can_create_project"`
Skype string `json:"skype",omitempty`
Twitter string `json:"twitter",omitempty`
LinkedIn string `json:"linkedin",omitempty`
WebsiteURL string `json:"website_url",omitempty`
PrivateToken string `json:"private_token"`
}
func (g *Gitlab) GetSession(email string, password string) (*Session, error) {
session_url := g.ResourceUrl(session_path, map[string]string{})
var session *Session
v := url.Values{}
v.Set("email", email)
v.Set("password", password)
body := v.Encode()
contents, err := g.buildAndExecRequest("POST", session_url, []byte(body))
if err != nil {
return session, err
}
err = json.Unmarshal(contents, &session)
return session, err
}

View file

@ -0,0 +1,33 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetSesison(t *testing.T) {
ts, gitlab := Stub("stubs/session/index.json")
session, err := gitlab.GetSession("john@example.com", "samplepassword")
assert.Equal(t, err, nil)
assert.Equal(t, session.Id, 1)
assert.Equal(t, session.UserName, "john_smith")
assert.Equal(t, session.Name, "John Smith")
assert.Equal(t, session.State, "active")
assert.Equal(t, session.AvatarURL, "http://someurl.com/avatar.png")
assert.Equal(t, session.IsAdmin, false)
assert.Equal(t, session.Bio, "somebio")
assert.Equal(t, session.Skype, "someskype")
assert.Equal(t, session.LinkedIn, "somelinkedin")
assert.Equal(t, session.Twitter, "sometwitter")
assert.Equal(t, session.WebsiteURL, "http://example.com")
assert.Equal(t, session.Email, "john@example.com")
assert.Equal(t, session.ThemeId, 1)
assert.Equal(t, session.ColorSchemeId, 1)
assert.Equal(t, session.ExternUid, "someuid")
assert.Equal(t, session.Provider, "github.com")
assert.Equal(t, session.CanCreateGroup, true)
assert.Equal(t, session.CanCreateProject, true)
assert.Equal(t, session.PrivateToken, "dd34asd13as")
defer ts.Close()
}

View file

@ -0,0 +1,26 @@
[
{
"name": "master",
"commit": {
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
"parents": [
{
"id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
}
],
"tree": "46e82de44b1061621357f24c05515327f2795a95",
"message": "add projects API",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "John Smith",
"email": "john@example.com"
},
"authored_date": "2012-06-27T05:51:39-07:00",
"committed_date": "2012-06-28T03:44:20-07:00"
},
"protected": true
}
]

View file

@ -0,0 +1,24 @@
{
"name": "master",
"commit": {
"id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
"parents": [
{
"id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
}
],
"tree": "46e82de44b1061621357f24c05515327f2795a95",
"message": "add projects API",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "John Smith",
"email": "john@example.com"
},
"authored_date": "2012-06-27T05:51:39-07:00",
"committed_date": "2012-06-28T03:44:20-07:00"
},
"protected": true
}

View file

@ -0,0 +1,18 @@
[
{
"id": "ed899a2f4b50b4370feeea94676502b42383c746",
"short_id": "ed899a2f4b5",
"title": "Replace sanitize with escape once",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dzaporozhets@sphereconsultinginc.com",
"created_at": "2012-09-20T11:50:22+03:00"
},
{
"id": "6104942438c14ec7bd21c6cd5bd995272b3faff6",
"short_id": "6104942438c",
"title": "Sanitize for network graph",
"author_name": "randx",
"author_email": "dmitriy.zaporozhets@gmail.com",
"created_at": "2012-09-20T09:06:12+03:00"
}
]

View file

@ -0,0 +1,18 @@
{
"object_kind": "issue",
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
"assignee_id": 51,
"author_id": 51,
"project_id": 14,
"created_at": "2013-12-03T17:15:43Z",
"updated_at": "2013-12-03T17:15:43Z",
"position": 0,
"branch_name": null,
"description": "Create new API for manipulations with repository",
"milestone_id": null,
"state": "opened",
"iid": 23
}
}

View file

@ -0,0 +1,22 @@
{
"object_kind": "merge_request",
"object_attributes": {
"id": 99,
"target_branch": "master",
"source_branch": "ms-viewport",
"source_project_id": 14,
"author_id": 51,
"assignee_id": 6,
"title": "MS-Viewport",
"created_at": "2013-12-03T17:23:34Z",
"updated_at": "2013-12-03T17:23:34Z",
"st_commits": null,
"st_diffs": null,
"milestone_id": null,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 14,
"iid": 1,
"description": ""
}
}

View file

@ -0,0 +1,42 @@
{
"object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"user_id": 4,
"user_name": "John Smith",
"user_email": "john@example.com",
"project_id": 15,
"repository": {
"name": "Diaspora",
"url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
"homepage": "http://example.com/mike/diaspora",
"git_http_url":"http://example.com/mike/diaspora.git",
"git_ssh_url":"git@example.com:mike/diaspora.git",
"visibility_level":0
},
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"message": "Update Catalan translation to e38cb41.",
"timestamp": "2011-12-12T14:27:31+02:00",
"url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author": {
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
}
},
{
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
"timestamp": "2012-01-03T23:36:29+02:00",
"url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
}
}
],
"total_commits_count": 4
}

View file

@ -0,0 +1,9 @@
{
"id": 1,
"url": "http://example.com/hook",
"project_id": 3,
"push_events": "true",
"issues_events": "true",
"merge_requests_events": "true",
"created_at": "2012-10-12T17:04:47Z"
}

View file

@ -0,0 +1,50 @@
[
{
"name": "async",
"commit": {
"id": "a2b702edecdf41f07b42653eb1abe30ce98b9fca",
"parents": [
{
"id": "3f94fc7c85061973edc9906ae170cc269b07ca55"
}
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
"message": "give caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2010-12-08T21:28:50+00:00",
"committed_date": "2010-12-08T21:28:50+00:00"
},
"protected": false
},
{
"name": "gh-pages",
"commit": {
"id": "101c10a60019fe870d21868835f65c25d64968fc",
"parents": [
{
"id": "9c15d2e26945a665131af5d7b6d30a06ba338aaa"
}
],
"tree": "fb5cc9d45da3014b17a876ad539976a0fb9b352a",
"message": "Underscore.js 1.5.2",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"committer": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
"authored_date": "2013-09-07T12: 58: 21+00: 00",
"committed_date": "2013-09-07T12: 58: 21+00: 00"
},
"protected": false
}
]

View file

@ -0,0 +1,72 @@
[
{
"id": 4,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
"web_url": "http://example.com/diaspora/diaspora-client",
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
}
},
{
"id": 6,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
"http_url_to_repo": "http://example.com/brightbox/puppet.git",
"web_url": "http://example.com/brightbox/puppet",
"owner": {
"id": 4,
"name": "Brightbox",
"created_at": "2013-09-30T13:46:02Z"
},
"name": "Puppet",
"name_with_namespace": "Brightbox / Puppet",
"path": "puppet",
"path_with_namespace": "brightbox/puppet",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"namespace": {
"created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 4,
"name": "Brightbox",
"owner_id": 1,
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
}
}
]

View file

@ -0,0 +1,30 @@
[
{
"id": 1,
"iid": 1,
"target_branch": "master",
"source_branch": "test1",
"project_id": 3,
"title": "test1",
"state": "opened",
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
"assignee": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"name": "Administrator",
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
"description":"fixed login page css paddings"
}
]

View file

@ -0,0 +1,45 @@
{
"id": 3,
"description": null,
"default_branch": "master",
"public": false,
"visibility_level": 0,
"ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
"http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
"web_url": "http://example.com/diaspora/diaspora-project-site",
"owner": {
"id": 3,
"name": "Diaspora",
"created_at": "2013-09-30T13: 46: 02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
"merge_requests_enabled": true,
"wall_enabled": false,
"wiki_enabled": true,
"snippets_enabled": false,
"created_at": "2013-09-30T13: 46: 02Z",
"last_activity_at": "2013-09-30T13: 46: 02Z",
"namespace": {
"created_at": "2013-09-30T13: 46: 02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
"permissions": {
"project_access": {
"access_level": 10,
"notification_level": 3
},
"group_access": {
"access_level": 50,
"notification_level": 3
}
}
}

View file

@ -0,0 +1,12 @@
[
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
},
{
"id": 3,
"title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}
]

View file

@ -0,0 +1,5 @@
{
"id": 1,
"title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
}

View file

@ -0,0 +1,23 @@
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"avatar_url": "http://someurl.com/avatar.png",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": "somebio",
"skype": "someskype",
"linkedin": "somelinkedin",
"twitter": "sometwitter",
"website_url": "http://example.com",
"theme_id": 1,
"color_scheme_id": 1,
"extern_uid": "someuid",
"provider": "github.com",
"is_admin": false,
"can_create_group": true,
"can_create_team": true,
"can_create_project": true,
"private_token": "dd34asd13as"
}

View file

@ -0,0 +1,22 @@
[
{
"name": "v1.0.0",
"commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"parents": [],
"tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
"message": "Initial commit",
"author": {
"name": "John Smith",
"email": "john@example.com"
},
"committer": {
"name": "Jack Smith",
"email": "jack@example.com"
},
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00"
},
"protected": null
}
]

View file

@ -0,0 +1,19 @@
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"private_token": "dd34asd13as",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"theme_id": 1,
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
"can_create_project": true
}

View file

@ -0,0 +1,41 @@
[
{
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith",
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"extern_uid": "john.smith",
"provider": "provider_name",
"theme_id": 1,
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true
},
{
"id": 2,
"username": "jack_smith",
"email": "jack@example.com",
"name": "Jack Smith",
"state": "blocked",
"created_at": "2012-05-23T08:01:01Z",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"website_url": "",
"extern_uid": "jack.smith",
"provider": "provider_name",
"theme_id": 1,
"color_scheme_id": 3,
"is_admin": false,
"can_create_group": true,
"can_create_project": true
}
]

View file

@ -0,0 +1,15 @@
{
"id": 6,
"username": "plouc",
"email": "plouc@plouc.com",
"name": "Raphaël Benitte",
"bio": null,
"skype": "",
"linkedin": "",
"twitter": "",
"theme_id": 2,
"state": "active",
"created_at": "2001-01-01T00:00:00Z",
"extern_uid": "uid=plouc",
"provider": "ldap"
}

View file

@ -0,0 +1,97 @@
package gogitlab
import (
"encoding/json"
"strconv"
)
const (
users_url = "/users" // Get users list
user_url = "/users/:id" // Get a single user.
current_user_url = "/user" // Get current user
)
type User struct {
Id int `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
AvatarUrl string `json:"avatar_url,omitempty"`
Name string `json:"name,omitempty"`
State string `json:"state,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
Bio string `json:"bio,omitempty"`
Skype string `json:"skype,omitempty"`
LinkedIn string `json:"linkedin,omitempty"`
Twitter string `json:"twitter,omitempty"`
ExternUid string `json:"extern_uid,omitempty"`
Provider string `json:"provider,omitempty"`
ThemeId int `json:"theme_id,omitempty"`
ColorSchemeId int `json:"color_scheme_id,color_scheme_id"`
}
func (g *Gitlab) Users(page int, per_page int) ([]*User, error) {
qry := map[string]string{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page)}
url := g.ResourceUrlQuery(users_url, nil, qry)
var users []*User
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &users)
}
return users, err
}
/*
Get a single user.
GET /users/:id
Parameters:
id The ID of a user
Usage:
user, err := gitlab.User("your_user_id")
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%+v\n", user)
*/
func (g *Gitlab) User(id string) (*User, error) {
url := g.ResourceUrl(user_url, map[string]string{":id": id})
user := new(User)
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
}
func (g *Gitlab) DeleteUser(id string) error {
url := g.ResourceUrl(user_url, map[string]string{":id": id})
var err error
_, err = g.buildAndExecRequest("DELETE", url, nil)
return err
}
func (g *Gitlab) CurrentUser() (User, error) {
url := g.ResourceUrl(current_user_url, nil)
var user User
contents, err := g.buildAndExecRequest("GET", url, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
}

View file

@ -0,0 +1,53 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestUsers(t *testing.T) {
ts, gitlab := Stub("stubs/users/index.json")
users, err := gitlab.Users(0, 100)
assert.Equal(t, err, nil)
assert.Equal(t, len(users), 2)
defer ts.Close()
}
func TestUser(t *testing.T) {
ts, gitlab := Stub("stubs/users/show.json")
user, err := gitlab.User("plouc")
assert.Equal(t, err, nil)
assert.IsType(t, new(User), user)
assert.Equal(t, user.Id, 6)
assert.Equal(t, user.Username, "plouc")
assert.Equal(t, user.Name, "Raphaël Benitte")
assert.Equal(t, user.Bio, "")
assert.Equal(t, user.Skype, "")
assert.Equal(t, user.LinkedIn, "")
assert.Equal(t, user.Twitter, "")
assert.Equal(t, user.ThemeId, 2)
assert.Equal(t, user.State, "active")
assert.Equal(t, user.CreatedAt, "2001-01-01T00:00:00Z")
assert.Equal(t, user.ExternUid, "uid=plouc")
assert.Equal(t, user.Provider, "ldap")
defer ts.Close()
}
func TestDeleteUser(t *testing.T) {
ts, gitlab := Stub("")
err := gitlab.DeleteUser("1")
assert.Equal(t, err, nil)
defer ts.Close()
}
func TestCurrentUser(t *testing.T) {
ts, gitlab := Stub("stubs/users/current.json")
user, err := gitlab.CurrentUser()
assert.Equal(t, err, nil)
assert.Equal(t, user.Username, "john_smith")
defer ts.Close()
}

View file

@ -0,0 +1,10 @@
package gogitlab
import (
"net/url"
"strings"
)
func encodeParameter(value string) string {
return strings.Replace(url.QueryEscape(value), "/", "%2F", 0)
}

View file

@ -0,0 +1,11 @@
package gogitlab
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestParameterEncoding(t *testing.T) {
assert.Equal(t, encodeParameter("namespace/project"), "namespace%2Fproject")
assert.Equal(t, encodeParameter("14"), "14")
}

View file

@ -9,7 +9,7 @@ import (
"github.com/drone/drone/Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs"
"github.com/drone/drone/pkg/config"
"github.com/drone/drone/pkg/remote/builtin/github"
"github.com/drone/drone/pkg/remote"
"github.com/drone/drone/pkg/server"
"github.com/drone/drone/pkg/server/session"
@ -19,6 +19,8 @@ import (
runner "github.com/drone/drone/pkg/runner/builtin"
"github.com/drone/drone/pkg/store"
_ "github.com/drone/drone/pkg/remote/builtin/github"
_ "github.com/drone/drone/pkg/remote/builtin/gitlab"
_ "github.com/drone/drone/pkg/store/builtin"
_ "net/http/pprof"
@ -49,7 +51,10 @@ func main() {
panic(err)
}
remote := github.New(settings)
remote, err := remote.New(settings.Remote.Driver, settings)
if err != nil {
panic(err)
}
session := session.New(settings)
eventbus_ := eventbus.New()
queue_ := queue.New()

View file

@ -1,20 +1,20 @@
package config
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/drone/drone/Godeps/_workspace/src/github.com/naoina/toml"
"github.com/drone/drone/Godeps/_workspace/src/github.com/vrischmann/envconfig"
)
type Config struct {
Remote struct {
Kind string `envconfig:"optional"`
Base string `envconfig:"optional"`
Orgs []string `envconfig:"optional"`
Open bool `envconfig:"optional"`
Private bool `envconfig:"optional"`
SkipVerify bool `envconfig:"optional"`
Driver string `envconfig:"optional"`
}
Auth struct {
@ -67,20 +67,14 @@ type Config struct {
Plugins []string `envconfig:"optional"`
Github struct {
Client string `envconfig:"optional"`
Secret string `envconfig:"optional"`
Orgs []string `envconfig:"optional"`
Open bool `envconfig:"optional"`
}
GithubEnterprise struct {
URL string `envconfig:"optional"`
Client string `envconfig:"optional"`
Secret string `envconfig:"optional"`
Private bool `envconfig:"optional"`
SkipVerify bool `envconfig:"optional"`
Open bool `envconfig:"optional"`
Orgs []string `envconfig:"optional"`
API string `envconfig:"optional"`
Host string `envconfig:"optional"`
Client string `envconfig:"optional"`
Secret string `envconfig:"optional"`
PrivateMode bool `envconfig:"optional"`
SkipVerify bool `envconfig:"optional"`
Open bool `envconfig:"optional"`
Orgs []string `envconfig:"optional"`
}
Bitbucket struct {
@ -91,7 +85,7 @@ type Config struct {
}
Gitlab struct {
URL string `envconfig:"optional"`
Host string `envconfig:"optional"`
Client string `envconfig:"optional"`
Secret string `envconfig:"optional"`
SkipVerify bool `envconfig:"optional"`
@ -132,5 +126,34 @@ func applyDefaults(c *Config) *Config {
if len(c.Session.Secret) == 0 {
c.Session.Secret = c.Auth.Secret
}
// Prevent crash on start, use sqlite3
// driver as default if DRONE_DATABASE_DRIVER and
// DRONE_DATABASE_DATASOURCE not specifed
if len(c.Database.Driver) == 0 && len(c.Database.Datasource) == 0 {
c.Database.Driver = "sqlite3"
pwd, err := os.Getwd()
if err != nil {
panic(err)
}
c.Database.Datasource = path.Join(pwd, "drone.sqlite3")
log.Warnf("Use default database settings, driver: %q, config: %q", c.Database.Driver, c.Database.Datasource)
}
// Set default settings for remotes
switch strings.ToLower(c.Remote.Driver) {
case "github":
if len(c.Github.API) == 0 && len(c.Github.Host) == 0 {
c.Github.API = "https://api.github.com/"
c.Github.Host = "https://github.com"
log.Warnf("Use default github settings, host: %q, api: %q", c.Github.Host, c.Github.API)
} else if len(c.Github.API) == 0 && len(c.Github.Host) != 0 {
c.Github.API = fmt.Sprintf("%s/api/v3/", c.Github.Host)
log.Warnf("Github API not specified, use: %q", c.Github.API)
}
}
return c
}

View file

@ -1,6 +1,7 @@
package github
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
@ -10,13 +11,15 @@ import (
"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"
"github.com/drone/drone/Godeps/_workspace/src/github.com/google/go-github/github"
)
const (
DefaultAPI = "https://api.github.com/"
DefaultURL = "https://github.com"
DefaultScope = "repo,repo:status,user:email"
)
@ -26,33 +29,35 @@ type GitHub struct {
API string
Client string
Secret string
AllowedOrgs []string
Open bool
PrivateMode bool
SkipVerify bool
cache *lru.Cache
}
func New(conf *config.Config) *GitHub {
func init() {
remote.Register("github", NewDriver)
}
func NewDriver(conf *config.Config) (remote.Remote, error) {
var github = GitHub{
API: DefaultAPI,
URL: DefaultURL,
Client: conf.Auth.Client,
Secret: conf.Auth.Secret,
PrivateMode: conf.Remote.Private,
SkipVerify: conf.Remote.SkipVerify,
API: conf.Github.API,
URL: conf.Github.Host,
Client: conf.Github.Client,
Secret: conf.Github.Secret,
AllowedOrgs: conf.Github.Orgs,
Open: conf.Github.Open,
PrivateMode: conf.Github.PrivateMode,
SkipVerify: conf.Github.SkipVerify,
}
var err error
github.cache, err = lru.New(1028)
if err != nil {
panic(err)
return nil, err
}
// if GitHub enterprise then ensure we're using the
// appropriate URLs
if !strings.HasPrefix(conf.Remote.Base, DefaultURL) && len(conf.Remote.Base) != 0 {
github.URL = conf.Remote.Base
github.API = conf.Remote.Base + "/api/v3/"
}
// the API must have a trailing slash
if !strings.HasSuffix(github.API, "/") {
github.API += "/"
@ -61,7 +66,7 @@ func New(conf *config.Config) *GitHub {
if strings.HasSuffix(github.URL, "/") {
github.URL = github.URL[:len(github.URL)-1]
}
return &github
return &github, nil
}
func (g *GitHub) Login(token, secret string) (*common.User, error) {
@ -92,6 +97,16 @@ func (g *GitHub) Orgs(u *common.User) ([]string, error) {
return orgs_, nil
}
// Accessor method, to allowed remote organizations field.
func (g *GitHub) GetOrgs() []string {
return g.AllowedOrgs
}
// Accessor method, to open field.
func (g *GitHub) GetOpen() bool {
return g.Open
}
// Repo fetches the named repository from the remote system.
func (g *GitHub) Repo(u *common.User, owner, name string) (*common.Repo, error) {
client := NewClient(g.API, u.Token, g.SkipVerify)
@ -281,6 +296,25 @@ func (g *GitHub) push(r *http.Request) (*common.Hook, error) {
return &common.Hook{Repo: repo, Commit: commit}, nil
}
// ¯\_(ツ)_/¯
func (g *GitHub) Oauth2Transport(r *http.Request) *oauth2.Transport {
return &oauth2.Transport{
Config: &oauth2.Config{
ClientId: g.Client,
ClientSecret: g.Secret,
Scope: DefaultScope,
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL),
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL),
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},
},
}
}
// pullRequest parses a hook with event type `pullRequest`
// and returns the commit data.
func (g *GitHub) pullRequest(r *http.Request) (*common.Hook, error) {

View file

@ -0,0 +1,321 @@
package gitlab
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/drone/drone/Godeps/_workspace/src/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 (
DefaultScope = "api"
)
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{
URL: conf.Gitlab.Host,
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
} else {
repo.Private = !repo_.Public
}
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")
}
// 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.
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.
// NOTE gitlab does not support this, so now we skip this.
func (r *Gitlab) Netrc(u *common.User) (*common.Netrc, error) {
return nil, nil
}
// Activate activates a repository by adding a Post-commit hook and
// a Public Deploy key, if applicable.
func (r *Gitlab) Activate(user *common.User, repo *common.Repo, k *common.Keypair, link string) error {
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 {
if err := client.AddProjectDeployKey(path, title, k.Public); err != nil {
return err
}
}
// append the repo owner / name to the hook url since gitlab
// doesn't send this detail in the post-commit hook
link += "&owner=" + repo.Owner + "&name=" + repo.Name
// 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
}
link += "&owner=" + repo.Owner + "&name=" + repo.Name
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) {
defer req.Body.Close()
var payload, _ = ioutil.ReadAll(req.Body)
var parsed, err = gogitlab.ParseHook(payload)
if err != nil {
return nil, err
}
if len(parsed.After) == 0 || parsed.TotalCommitsCount == 0 {
return nil, nil
}
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
}
if len(parsed.After) == 0 {
return nil, nil
}
var cloneUrl = parsed.Repository.GitHttpUrl
if parsed.Repository.VisibilityLevel < 20 {
cloneUrl = parsed.Repository.GitSshUrl
}
var hook = new(common.Hook)
hook.Repo = &common.Repo{}
hook.Repo.Owner = req.FormValue("owner")
hook.Repo.Name = req.FormValue("name")
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{}
hook.Commit.Sha = parsed.After
hook.Commit.Branch = parsed.Branch()
hook.Commit.Ref = parsed.Ref
hook.Commit.Remote = cloneUrl
var head = parsed.Head()
hook.Commit.Message = head.Message
hook.Commit.Timestamp = head.Timestamp
hook.Commit.Author = &common.Author{}
// extracts the commit author (ideally email)
// from the post-commit hook
switch {
case head.Author != nil:
hook.Commit.Author.Email = head.Author.Email
hook.Commit.Author.Login = parsed.UserName
case head.Author == nil:
hook.Commit.Author.Login = parsed.UserName
}
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),
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
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
}

View file

@ -0,0 +1,84 @@
package gitlab
import (
"fmt"
"net/url"
"github.com/drone/drone/Godeps/_workspace/src/github.com/Bugagazavr/go-gitlab-client"
)
// NewClient is a helper function that returns a new GitHub
// client using the provided OAuth token.
func NewClient(url, accessToken string, skipVerify bool) *gogitlab.Gitlab {
client := gogitlab.NewGitlabCert(url, "/api/v3", accessToken, skipVerify)
client.Bearer = true
return client
}
// IsRead is a helper function that returns true if the
// user has Read-only access to the repository.
func IsRead(proj *gogitlab.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
switch {
case proj.Public:
return true
case user != nil && user.AccessLevel >= 20:
return true
case group != nil && group.AccessLevel >= 20:
return true
default:
return false
}
}
// IsWrite is a helper function that returns true if the
// user has Read-Write access to the repository.
func IsWrite(proj *gogitlab.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
switch {
case user != nil && user.AccessLevel >= 30:
return true
case group != nil && group.AccessLevel >= 30:
return true
default:
return false
}
}
// IsAdmin is a helper function that returns true if the
// user has Admin access to the repository.
func IsAdmin(proj *gogitlab.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
switch {
case user != nil && user.AccessLevel >= 40:
return true
case group != nil && group.AccessLevel >= 40:
return true
default:
return false
}
}
// GetKeyTitle is a helper function that generates a title for the
// RSA public key based on the username and domain name.
func GetKeyTitle(rawurl string) (string, error) {
var uri, err = url.Parse(rawurl)
if err != nil {
return "", err
}
return fmt.Sprintf("drone@%s", uri.Host), nil
}
func ns(owner, name string) string {
return fmt.Sprintf("%s%%2F%s", owner, name)
}
func GetUserEmail(client *gogitlab.Gitlab, defaultURL string) (*gogitlab.Gitlab, error) {
return client, nil
}

View file

@ -1,11 +1,42 @@
package remote
import (
"fmt"
"net/http"
"github.com/drone/drone/pkg/config"
"github.com/drone/drone/pkg/oauth2"
common "github.com/drone/drone/pkg/types"
)
var drivers = make(map[string]DriverFunc)
// Register makes a remote driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver DriverFunc) {
if driver == nil {
panic("remote: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("remote: Register called twice for driver " + name)
}
drivers[name] = driver
}
// DriverFunc returns a new connection to the remote.
// Config is a struct, with base remote configuration.
type DriverFunc func(conf *config.Config) (Remote, error)
// New creates a new remote connection.
func New(driver string, conf *config.Config) (Remote, error) {
fn, ok := drivers[driver]
if !ok {
return nil, fmt.Errorf("remote: unknown driver %q", driver)
}
return fn(conf)
}
type Remote interface {
// Login authenticates the session and returns the
// remote user details.
@ -45,6 +76,16 @@ type Remote interface {
// and returns the required data in a standard format.
Hook(r *http.Request) (*common.Hook, error)
// Oauth2Transport
Oauth2Transport(r *http.Request) *oauth2.Transport
// GetOrgs returns all allowed organizations for remote.
GetOrgs() []string
// GetOpen returns boolean field with enabled or disabled
// registration.
GetOpen() bool
// Default scope for remote
Scope() string
}

View file

@ -1,17 +1,13 @@
package server
import (
"fmt"
"strings"
"time"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/ungerik/go-gravatar"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/drone/drone/pkg/oauth2"
common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil"
)
// GetLogin accepts a request to authorize the user and to
@ -52,9 +48,9 @@ func GetLogin(c *gin.Context) {
login := ToUser(c)
// check organization membership, if applicable
if len(settings.Remote.Orgs) != 0 {
if len(remote.GetOrgs()) != 0 {
orgs, _ := remote.Orgs(login)
if !checkMembership(orgs, settings.Remote.Orgs) {
if !checkMembership(orgs, remote.GetOrgs()) {
c.Redirect(303, "/login#error=access_denied_org")
return
}
@ -73,7 +69,7 @@ func GetLogin(c *gin.Context) {
// if self-registration is disabled we should
// return a notAuthorized error. the only exception
// is if no users exist yet in the system we'll proceed.
if !settings.Remote.Open && count != 0 {
if !remote.GetOpen() && count != 0 {
log.Errorf("cannot register %s. registration closed", login.Login)
c.Redirect(303, "/login#error=access_denied")
return
@ -131,35 +127,26 @@ func GetLogin(c *gin.Context) {
// getLoginOauth2 is the default authorization implementation
// using the oauth2 protocol.
func getLoginOauth2(c *gin.Context) {
var settings = ToSettings(c)
var remote = ToRemote(c)
var scope = strings.Join(settings.Auth.Scope, ",")
if scope == "" {
scope = remote.Scope()
}
var config = &oauth2.Config{
ClientId: settings.Auth.Client,
ClientSecret: settings.Auth.Secret,
Scope: scope,
AuthURL: settings.Auth.Authorize,
TokenURL: settings.Auth.AccessToken,
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(c.Request)),
//settings.Server.Scheme, settings.Server.Hostname),
}
// Bugagazavr: I think this must be moved to remote config
//var scope = strings.Join(settings.Auth.Scope, ",")
//if scope == "" {
// scope = remote.Scope()
//}
var transport = remote.Oauth2Transport(c.Request)
// get the OAuth code
var code = c.Request.FormValue("code")
//var state = c.Request.FormValue("state")
if len(code) == 0 {
// TODO this should be a random number, verified by a cookie
c.Redirect(303, config.AuthCodeURL("random"))
c.Redirect(303, transport.AuthCodeURL("random"))
return
}
// exhange for a token
var trans = &oauth2.Transport{Config: config}
var token, err = trans.Exchange(code)
var token, err = transport.Exchange(code)
if err != nil {
log.Errorf("cannot get access_token. %s", err)
c.Redirect(303, "/login#error=token_exchange")