Merge pull request #1374 from Bugagazavr/embedded-gitlab-client

Move gitlab client inside drone
This commit is contained in:
Brad Rydzewski 2016-01-13 12:43:39 -08:00
commit bff5834677
9 changed files with 559 additions and 15 deletions

View file

@ -0,0 +1,27 @@
package client
const (
droneServiceUrl = "/projects/:id/services/drone-ci"
)
func (c *Client) AddDroneService(id string, params QMap) error {
url, opaque := c.ResourceUrl(
droneServiceUrl,
QMap{":id": id},
params,
)
_, err := c.Do("PUT", url, opaque, nil)
return err
}
func (c *Client) DeleteDroneService(id string) error {
url, opaque := c.ResourceUrl(
droneServiceUrl,
QMap{":id": id},
nil,
)
_, err := c.Do("DELETE", url, opaque, nil)
return err
}

View file

@ -0,0 +1,96 @@
package client
import (
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
type Client struct {
BaseUrl string
ApiPath string
Token string
Client *http.Client
}
func New(baseUrl, apiPath, token string, skipVerify bool) *Client {
config := &tls.Config{InsecureSkipVerify: skipVerify}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: config,
}
client := &http.Client{Transport: tr}
return &Client{
BaseUrl: baseUrl,
ApiPath: apiPath,
Token: token,
Client: client,
}
}
func (c *Client) ResourceUrl(u string, params, query QMap) (string, string) {
if params != nil {
for key, val := range params {
u = strings.Replace(u, key, encodeParameter(val), -1)
}
}
query_params := url.Values{}
if query != nil {
for key, val := range query {
query_params.Set(key, val)
}
}
u = c.BaseUrl + c.ApiPath + u + "?" + query_params.Encode()
p, err := url.Parse(u)
if err != nil {
return u, ""
}
opaque := "//" + p.Host + p.Path
return u, opaque
}
func (c *Client) Do(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 {
return nil, fmt.Errorf("Error while building gitlab request")
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token))
if len(opaque) > 0 {
req.URL.Opaque = opaque
}
resp, err := c.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
}

View file

@ -0,0 +1,41 @@
package client
import (
"encoding/json"
"fmt"
)
// 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
}

View file

@ -0,0 +1,138 @@
package client
import (
"encoding/json"
"strconv"
"strings"
)
const (
searchUrl = "/projects/search/:query"
projectsUrl = "/projects"
projectUrl = "/projects/:id"
repoUrlRawFile = "/projects/:id/repository/blobs/:sha"
commitStatusUrl = "/projects/:id/statuses/:sha"
)
// Get a list of all projects owned by the authenticated user.
func (g *Client) 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 (c *Client) Projects(page int, per_page int) ([]*Project, error) {
url, opaque := c.ResourceUrl(projectsUrl, nil, QMap{
"page": strconv.Itoa(page),
"per_page": strconv.Itoa(per_page),
})
var projects []*Project
contents, err := c.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &projects)
}
return projects, err
}
// Get a project by id
func (c *Client) Project(id string) (*Project, error) {
url, opaque := c.ResourceUrl(projectUrl, QMap{":id": id}, nil)
var project *Project
contents, err := c.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &project)
}
return project, err
}
// Get Raw file content
func (c *Client) RepoRawFile(id, sha, filepath string) ([]byte, error) {
url, opaque := c.ResourceUrl(
repoUrlRawFile,
QMap{
":id": id,
":sha": sha,
},
QMap{
"filepath": filepath,
},
)
contents, err := c.Do("GET", url, opaque, nil)
return contents, err
}
//
func (c *Client) SetStatus(id, sha, state, desc, ref, link string) error {
url, opaque := c.ResourceUrl(
commitStatusUrl,
QMap{
":id": id,
":sha": sha,
},
QMap{
"state": state,
"ref": ref,
"target_url": link,
"description": desc,
"context": "ci/drone",
},
)
_, err := c.Do("POST", url, opaque, nil)
return err
}
// Get a list of projects by query owned by the authenticated user.
func (c *Client) SearchProjectId(namespace string, name string) (id int, err error) {
url, opaque := c.ResourceUrl(searchUrl, nil, QMap{
":query": strings.ToLower(name),
})
var projects []*Project
contents, err := c.Do("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 && strings.ToLower(project.Name) == strings.ToLower(name) {
id = project.Id
}
}
return id, err
}

View file

@ -0,0 +1,119 @@
package client
type QMap map[string]string
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"`
}
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"`
}
type Project struct {
Id int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
Public bool `json:"public,omitempty"`
Path string `json:"path,omitempty"`
PathWithNamespace string `json:"path_with_namespace,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 Namespace struct {
Id int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type Person struct {
Name string `json:"name"`
Email string `json:"email"`
}
type hProject struct {
Name string `json:"name"`
SshUrl string `json:"ssh_url"`
HttpUrl string `json:"http_url"`
VisibilityLevel int `json:"visibility_level"`
WebUrl string `json:"web_url"`
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 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"`
Url string `json:"url,omiyempty"`
Source *hProject `json:"source,omitempty"`
Target *hProject `json:"target,omitempty"`
LastCommit *hCommit `json:"last_commit,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"`
}

View file

@ -0,0 +1,21 @@
package client
import (
"encoding/json"
)
const (
currentUserUrl = "/user"
)
func (c *Client) CurrentUser() (User, error) {
url, opaque := c.ResourceUrl(currentUserUrl, nil, nil)
var user User
contents, err := c.Do("GET", url, opaque, nil)
if err == nil {
err = json.Unmarshal(contents, &user)
}
return user, err
}

View file

@ -0,0 +1,33 @@
package client
import (
"net/url"
"strings"
)
func encodeParameter(value string) string {
return strings.Replace(url.QueryEscape(value), "/", "%2F", 0)
}
// 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

@ -15,7 +15,7 @@ import (
"github.com/drone/drone/shared/oauth2"
"github.com/drone/drone/shared/token"
"github.com/Bugagazavr/go-gitlab-client"
"github.com/drone/drone/remote/gitlab/client"
)
const (
@ -236,6 +236,22 @@ func (g *Gitlab) Script(user *model.User, repo *model.Repo, build *model.Build)
// 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 (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link string) error {
client := NewClient(g.URL, u.Token, g.SkipVerify)
status := getStatus(b.Status)
desc := getDesc(b.Status)
client.SetStatus(
ns(repo.Owner, repo.Name),
b.Commit,
status,
desc,
strings.Replace(b.Ref, "refs/heads/", "", -1),
link,
)
// Gitlab statuses it's a new feature, just ignore error
// if gitlab version not support this
return nil
}
@ -303,7 +319,7 @@ func (g *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) err
func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) {
defer req.Body.Close()
var payload, _ = ioutil.ReadAll(req.Body)
var parsed, err = gogitlab.ParseHook(payload)
var parsed, err = client.ParseHook(payload)
if err != nil {
return nil, nil, err
}
@ -318,7 +334,7 @@ func (g *Gitlab) Hook(req *http.Request) (*model.Repo, *model.Build, error) {
}
}
func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
func mergeRequest(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
repo := &model.Repo{}
repo.Owner = req.FormValue("owner")
@ -351,7 +367,7 @@ func mergeRequest(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo,
return repo, build, nil
}
func push(parsed *gogitlab.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
func push(parsed *client.HookPayload, req *http.Request) (*model.Repo, *model.Build, error) {
var cloneUrl = parsed.Repository.GitHttpUrl
repo := &model.Repo{}
@ -438,3 +454,57 @@ func (g *Gitlab) Scope() string {
func (g *Gitlab) String() string {
return "gitlab"
}
const (
StatusPending = "pending"
StatusRunning = "running"
StatusSuccess = "success"
StatusFailure = "failed"
StatusCanceled = "canceled"
)
const (
DescPending = "this build is pending"
DescRunning = "this buils is running"
DescSuccess = "the build was successful"
DescFailure = "the build failed"
DescCanceled = "the build canceled"
)
// getStatus is a helper functin that converts a Drone
// status to a GitHub status.
func getStatus(status string) string {
switch status {
case model.StatusPending:
return StatusPending
case model.StatusRunning:
return StatusRunning
case model.StatusSuccess:
return StatusSuccess
case model.StatusFailure, model.StatusError:
return StatusFailure
case model.StatusKilled:
return StatusCanceled
default:
return StatusFailure
}
}
// getDesc is a helper function that generates a description
// message for the build based on the status.
func getDesc(status string) string {
switch status {
case model.StatusPending:
return DescPending
case model.StatusRunning:
return DescRunning
case model.StatusSuccess:
return DescSuccess
case model.StatusFailure, model.StatusError:
return DescFailure
case model.StatusKilled:
return DescCanceled
default:
return DescFailure
}
}

View file

@ -5,20 +5,19 @@ import (
"net/url"
"strconv"
"github.com/Bugagazavr/go-gitlab-client"
"github.com/drone/drone/remote/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
func NewClient(url, accessToken string, skipVerify bool) *client.Client {
client := client.New(url, "/api/v3", accessToken, skipVerify)
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 {
func IsRead(proj *client.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
@ -36,7 +35,7 @@ func IsRead(proj *gogitlab.Project) bool {
// IsWrite is a helper function that returns true if the
// user has Read-Write access to the repository.
func IsWrite(proj *gogitlab.Project) bool {
func IsWrite(proj *client.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
@ -52,7 +51,7 @@ func IsWrite(proj *gogitlab.Project) bool {
// IsAdmin is a helper function that returns true if the
// user has Admin access to the repository.
func IsAdmin(proj *gogitlab.Project) bool {
func IsAdmin(proj *client.Project) bool {
var user = proj.Permissions.ProjectAccess
var group = proj.Permissions.GroupAccess
@ -80,13 +79,13 @@ 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
func GetUserEmail(c *client.Client, defaultURL string) (*client.Client, error) {
return c, nil
}
func GetProjectId(r *Gitlab, client *gogitlab.Gitlab, owner, name string) (projectId string, err error) {
func GetProjectId(r *Gitlab, c *client.Client, owner, name string) (projectId string, err error) {
if r.Search {
_projectId, err := client.SearchProjectId(owner, name)
_projectId, err := c.SearchProjectId(owner, name)
if err != nil || _projectId == 0 {
return "", err
}