Skip to content

Implement logic for fetching multiple result "pages" from Gitlab and add some functions #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 97 additions & 16 deletions gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package gogitlab
import (
"bytes"
"crypto/tls"
"errors"
"flag"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -69,32 +70,112 @@ func (g *Gitlab) ResourceUrl(url string, params map[string]string) string {
}

func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, error) {
return g.buildAndExecRequestEx(method, url, "", body, false)
}

func (g *Gitlab) buildAndExecRequestEx(method, rawurl, opaque string, body []byte, followNextLink bool) ([]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)
}
nextUrl, err := url.Parse(rawurl)
if err != nil {
panic("Error while building gitlab request")
return nil, err
}

resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
if len(opaque) > 0 {
nextUrl.Opaque = opaque
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)

// Check if both body and followNextLink are set
if body != nil && followNextLink {
return nil, errors.New("Cannot body and followNextLink are mutually exclusive")
}

if resp.StatusCode >= 400 {
err = fmt.Errorf("*Gitlab.buildAndExecRequest failed: <%d> %s", resp.StatusCode, req.URL)
baseRequestPath := nextUrl.EscapedPath()
privateToken := nextUrl.Query().Get("private_token")
contentsBuffer := &bytes.Buffer{}
for nextUrl != nil {
if body != nil {
reader := bytes.NewReader(body)
req, err = http.NewRequest(method, nextUrl.String(), reader)
} else {
req, err = http.NewRequest(method, nextUrl.String(), nil)
}
if err != nil {
panic("Error while building gitlab request")
}

resp, err := g.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("Client.Do error: %q", err)
}
defer resp.Body.Close()
partialContents, 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)
}

if err != nil {
return nil, err
}

// Clear nextUrl
nextUrl = nil

// Check if we need to continue
linkHeader := resp.Header.Get("Link")
if followNextLink && linkHeader != "" {
linkHeaders := strings.Split(linkHeader, ",")
for _, link := range linkHeaders {
// Find next link
if strings.HasSuffix(link, "; rel=\"next\"") {
nextRawUrl := strings.Trim(strings.TrimSuffix(link, "; rel=\"next\""), " <>")
next, err := url.Parse(nextRawUrl)
if err != nil {
return nil, err
}

// Make sure we are targeting the same path
if next.EscapedPath() != baseRequestPath {
return nil, fmt.Errorf("Invalid next URL '%s' - path different (original path: %s)",
nextRawUrl, baseRequestPath)
}

// Re-set private_token in next...
queryValues := next.Query()
queryValues.Set("private_token", privateToken)
next.RawQuery = queryValues.Encode()
nextUrl = next
break
}
}

// At this point nextUrl might be set. If this is the case, check for a trailing closing bracket
// in partialContents and replace it with a comma.
if partialContents[len(partialContents)-1] != byte(']') {
return nil, errors.New("Cannot follow next URL: partial contents do not seem to be an array.")
}

// Remove leading bracket
partialContents = bytes.TrimPrefix(partialContents, []byte("["))
// Replace trailing closing bracket with a comma
partialContents[len(partialContents)-1] = byte(',')
}
contentsBuffer.Write(partialContents)
}

contents := contentsBuffer.Bytes()
if contents[0] != byte('[') {
contents = append([]byte{'['}, contents...)
}

if contents[len(contents)-1] == byte(',') {
contents[len(contents)-1] = byte(']')
}

return contents, err
Expand Down
20 changes: 20 additions & 0 deletions groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gogitlab

import "encoding/json"

const (
group_members_url = "/groups/:id/members"
)

func (g *Gitlab) GroupMembers(id string) ([]*Member, error) {
url, opaque := g.ResourceUrlRaw(group_members_url, map[string]string{":id": id})

var members []*Member

contents, err := g.buildAndExecRequestEx("GET", url, opaque, nil, true)
if err == nil {
err = json.Unmarshal(contents, &members)
}

return members, err
}
59 changes: 51 additions & 8 deletions projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package gogitlab

import (
"encoding/json"
"fmt"
"strconv"
)

const (
projects_url = "/projects" // Get a list of projects owned by the authenticated user
projects_all_url = "/projects/all" // Get a list of all projects (admin-only)
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
Expand All @@ -15,14 +17,39 @@ const (
project_url_member = "/projects/:id/members/:user_id" // Get project team member
)

type AccessLevel int

func (al AccessLevel) Name() string {
if name, ok := accessLevelNameMap[al]; ok {
return name
}
return fmt.Sprintf("Unknown(%d)", al)
}

const (
AccessLevelGuest AccessLevel = 10
AccessLevelReporter = 20
AccessLevelDeveloper = 30
AccessLevelMaster = 40
AccessLevelOwner = 50
)

var accessLevelNameMap = map[AccessLevel]string{
AccessLevelGuest: "guest",
AccessLevelReporter: "reporter",
AccessLevelDeveloper: "developer",
AccessLevelMaster: "master",
AccessLevelOwner: "owner",
}

type Member struct {
Id int
Username string
Email string
Name string
State string
CreatedAt string `json:"created_at,omitempty"`
// AccessLevel int
Id int
Username string
Email string
Name string
State string
CreatedAt string `json:"created_at,omitempty"`
AccessLevel AccessLevel `json:"access_level,omitempty"`
}

type Namespace struct {
Expand Down Expand Up @@ -72,6 +99,22 @@ func (g *Gitlab) Projects() ([]*Project, error) {
return projects, err
}

/*
Get a list of all projects (admin-only).
*/
func (g *Gitlab) AllProjects() ([]*Project, error) {
url := g.ResourceUrl(projects_all_url, nil)

var projects []*Project

contents, err := g.buildAndExecRequestEx("GET", url, "", nil, true)
if err == nil {
err = json.Unmarshal(contents, &projects)
}

return projects, err
}

/*
Remove a project.
*/
Expand Down Expand Up @@ -133,7 +176,7 @@ func (g *Gitlab) ProjectMembers(id string) ([]*Member, error) {

var members []*Member

contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil)
contents, err := g.buildAndExecRequestEx("GET", url, opaque, nil, true)
if err == nil {
err = json.Unmarshal(contents, &members)
}
Expand Down
20 changes: 17 additions & 3 deletions users.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import (
)

const (
users_url = "/users?page=:page&per_page=:per_page" // Get users list
user_url = "/users/:id" // Get a single user.
current_user_url = "/user" // Get current user
users_url = "/users?page=:page&per_page=:per_page" // Get users list
users_all_url = "/users" // Get all users
user_url = "/users/:id" // Get a single user.
current_user_url = "/user" // Get current user
)

type User struct {
Expand All @@ -28,6 +29,19 @@ type User struct {
ColorSchemeId int `json:"color_scheme_id,color_scheme_id"`
}

func (g *Gitlab) AllUsers() ([]*User, error) {
url := g.ResourceUrl(users_all_url, nil)

var users []*User

contents, err := g.buildAndExecRequestEx("GET", url, "", nil, true)
if err == nil {
err = json.Unmarshal(contents, &users)
}

return users, err
}

func (g *Gitlab) Users(page, per_page int) ([]*User, error) {

url := g.ResourceUrl(users_url, map[string]string{":page": strconv.Itoa(page), ":per_page": strconv.Itoa(per_page)})
Expand Down