diff --git a/remote/gitlab/client/drone.go b/remote/gitlab/client/drone.go new file mode 100644 index 000000000..8915e8455 --- /dev/null +++ b/remote/gitlab/client/drone.go @@ -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 +} diff --git a/remote/gitlab/client/gitlab.go b/remote/gitlab/client/gitlab.go new file mode 100644 index 000000000..a44946aee --- /dev/null +++ b/remote/gitlab/client/gitlab.go @@ -0,0 +1,92 @@ +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)) + + 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 +} diff --git a/remote/gitlab/client/hook.go b/remote/gitlab/client/hook.go new file mode 100644 index 000000000..0263e1008 --- /dev/null +++ b/remote/gitlab/client/hook.go @@ -0,0 +1,5 @@ +package client + +func ParseHook(payload []byte) (*HookPayload, error) { + return nil, nil +} diff --git a/remote/gitlab/client/project.go b/remote/gitlab/client/project.go new file mode 100644 index 000000000..49d58f109 --- /dev/null +++ b/remote/gitlab/client/project.go @@ -0,0 +1,116 @@ +package client + +import ( + "encoding/json" + "strconv" + "strings" +) + +const ( + searchUrl = "/projects/search/:query" + projectsUrl = "/projects" + projectUrl = "/projects/:id" + repoUrlRawFile = "/projects/:id/repository/blobs/: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 +} + +// 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 +} diff --git a/remote/gitlab/client/types.go b/remote/gitlab/client/types.go new file mode 100644 index 000000000..04cb99d23 --- /dev/null +++ b/remote/gitlab/client/types.go @@ -0,0 +1,145 @@ +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"` + 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"` +} + +type Member struct { + Id int + Username string + Email string + Name string + State string + CreatedAt string `json:"created_at,omitempty"` + // AccessLevel int +} + +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"` + 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 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"` +} diff --git a/remote/gitlab/client/user.go b/remote/gitlab/client/user.go new file mode 100644 index 000000000..bafc7c47d --- /dev/null +++ b/remote/gitlab/client/user.go @@ -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 +} diff --git a/remote/gitlab/client/util.go b/remote/gitlab/client/util.go new file mode 100644 index 000000000..7cc5c37f4 --- /dev/null +++ b/remote/gitlab/client/util.go @@ -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 +} diff --git a/remote/gitlab/gitlab.go b/remote/gitlab/gitlab.go index 1ae990071..e24a2532a 100644 --- a/remote/gitlab/gitlab.go +++ b/remote/gitlab/gitlab.go @@ -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 ( @@ -296,7 +296,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 } @@ -311,7 +311,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") @@ -344,7 +344,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{} diff --git a/remote/gitlab/helper.go b/remote/gitlab/helper.go index 25b7f1d5c..62edc0191 100644 --- a/remote/gitlab/helper.go +++ b/remote/gitlab/helper.go @@ -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 }