diff --git a/.travis.yml b/.travis.yml index 8dc59b5..d3e54cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ language: go + +sudo: false + go: - 1.1 - 1.2 - 1.3 - 1.4 -install: - - go get github.com/stretchr/testify/assert -script: - - go test -v + - 1.5 + - 1.6 + +install: make install + +script: make test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e2c144f --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: test run update format install + +install: + go get github.com/stretchr/testify/assert + go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v + go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v + +update: + go get -u all + +format: + gofmt -l -w -s . + go fix ./... + +test: + go test -v ./... + go vet ./... + exit `gofmt -l -s -e . | wc -l` diff --git a/README.md b/README.md index 217b7bb..9ba4fd0 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ go-gitlab-client is a simple client written in golang to consume gitlab API. ##features * - ###Projects [gitlab api doc](http://api.gitlab.org/projects.html) + ### Projects [gitlab api doc](http://doc.gitlab.com/ce/api/projects.html) * list projects * get single project * remove project * - ###Repositories [gitlab api doc](http://api.gitlab.org/repositories.html) + ### Repositories [gitlab api doc](http://doc.gitlab.com/ce/api/repositories.html) * list repository branches * get single repository branch * list project repository tags @@ -24,17 +24,24 @@ go-gitlab-client is a simple client written in golang to consume gitlab API. * add/get/edit/rm project hook * - ###Users [gitlab api doc](http://api.gitlab.org/users.html) + ### Users [gitlab api doc](http://api.gitlab.org/users.html) * get single user * manage user keys * - ###Deploy Keys [gitlab api doc](http://api.gitlab.org/deploy_keys.html) + ### Deploy Keys [gitlab api doc](http://doc.gitlab.com/ce/api/deploy_keys.html) * list project deploy keys * add/get/rm project deploy key - - +* + ### Builds [gitlab api doc](http://doc.gitlab.com/ce/api/builds.html) + * List project builds + * Get a single build + * List commit builds + * Get build artifacts + * Cancel a build + * Retry a build + * Erase a build ##Installation diff --git a/builds.go b/builds.go new file mode 100644 index 0000000..33c6d31 --- /dev/null +++ b/builds.go @@ -0,0 +1,159 @@ +package gogitlab + +import ( + "encoding/json" + "io" +) + +const ( + project_builds = "/projects/:id/builds" // List project builds + project_build = "/projects/:id/builds/:build_id" // Get a single build + project_commit_builds = "/projects/:id/repository/commits/:sha/builds" // List commit builds + project_build_artifacts = "/projects/:id/builds/:build_id/artifacts" // Get build artifacts + project_build_cancel = "/projects/:id/builds/:build_id/cancel" // Cancel a build + project_build_retry = "/projects/:id/builds/:build_id/retry" // Retry a build + project_build_erase = "/projects/:id/builds/:build_id/erase" // Erase a build +) + +type ArtifactsFile struct { + Filename string `json:"filename"` + Size int `json:"size"` +} + +type Build struct { + Id int `json:"id"` + ArtifactsFile ArtifactsFile `json:"artifacts_file"` + Commit Commit `json:"commit"` + CreatedAt string `json:"created_at"` + DownloadURL string `json:"download_url"` + FinishedAt string `json:"finished_at"` + Name string `json:"name"` + Ref string `json:"ref"` + Stage string `json:"stage"` + StartedAt string `json:"started_at"` + Status string `json:"status"` + Tag bool `json:"tag"` + User User `json:"user"` +} + +func (g *Gitlab) ProjectBuilds(id string) ([]*Build, error) { + url, opaque := g.ResourceUrlRaw(project_builds, map[string]string{ + ":id": id, + }) + + builds := make([]*Build, 0) + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return builds, err + } + + err = json.Unmarshal(contents, &builds) + + return builds, err +} + +func (g *Gitlab) ProjectCommitBuilds(id, sha1 string) ([]*Build, error) { + url, opaque := g.ResourceUrlRaw(project_commit_builds, map[string]string{ + ":id": id, + ":sha": sha1, + }) + + builds := make([]*Build, 0) + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return builds, err + } + + err = json.Unmarshal(contents, &builds) + + return builds, err +} + +func (g *Gitlab) ProjectBuild(id, buildId string) (*Build, error) { + url, opaque := g.ResourceUrlRaw(project_build, map[string]string{ + ":id": id, + ":build_id": buildId, + }) + + build := &Build{} + + contents, err := g.buildAndExecRequestRaw("GET", url, opaque, nil) + if err != nil { + return nil, err + } + + err = json.Unmarshal(contents, &build) + + return build, err +} + +func (g *Gitlab) ProjectBuildArtifacts(id, buildId string) (io.ReadCloser, error) { + url, _ := g.ResourceUrlRaw(project_build_artifacts, map[string]string{ + ":id": id, + ":build_id": buildId, + }) + + resp, err := g.execRequest("GET", url, nil) + + if err != nil { + return nil, err + } + + return resp.Body, nil +} + +func (g *Gitlab) ProjectCancelBuild(id, buildId string) (*Build, error) { + url, opaque := g.ResourceUrlRaw(project_build_cancel, map[string]string{ + ":id": id, + ":build_id": buildId, + }) + + build := &Build{} + + contents, err := g.buildAndExecRequestRaw("POST", url, opaque, nil) + if err != nil { + return nil, err + } + + err = json.Unmarshal(contents, &build) + + return build, err +} + +func (g *Gitlab) ProjectRetryBuild(id, buildId string) (*Build, error) { + url, opaque := g.ResourceUrlRaw(project_build_retry, map[string]string{ + ":id": id, + ":build_id": buildId, + }) + + build := &Build{} + + contents, err := g.buildAndExecRequestRaw("POST", url, opaque, nil) + if err != nil { + return nil, err + } + + err = json.Unmarshal(contents, &build) + + return build, err +} + +func (g *Gitlab) ProjectEraseBuild(id, buildId string) (*Build, error) { + url, opaque := g.ResourceUrlRaw(project_build_erase, map[string]string{ + ":id": id, + ":build_id": buildId, + }) + + build := &Build{} + + contents, err := g.buildAndExecRequestRaw("POST", url, opaque, nil) + if err != nil { + return nil, err + } + + err = json.Unmarshal(contents, &build) + + return build, err +} diff --git a/builds_test.go b/builds_test.go new file mode 100644 index 0000000..c90d625 --- /dev/null +++ b/builds_test.go @@ -0,0 +1,84 @@ +package gogitlab + +import ( + "github.com/stretchr/testify/assert" + "io/ioutil" + "testing" +) + +func TestProjectBuilds(t *testing.T) { + ts, gitlab := Stub("stubs/builds/list.json") + defer ts.Close() + + builds, err := gitlab.ProjectBuilds("3") + + assert.Nil(t, err) + assert.Equal(t, len(builds), 2) + assert.Equal(t, builds[0].ArtifactsFile.Filename, "artifacts.zip") +} + +func TestProjectCommitBuilds(t *testing.T) { + ts, gitlab := Stub("stubs/builds/commit_builds_list.json") + defer ts.Close() + + builds, err := gitlab.ProjectBuilds("3") + + assert.Nil(t, err) + assert.Equal(t, len(builds), 2) + assert.Equal(t, builds[1].User.AvatarUrl, "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon") +} + +func TestProjectBuild(t *testing.T) { + ts, gitlab := Stub("stubs/builds/build.json") + defer ts.Close() + + build, err := gitlab.ProjectBuild("3", "12") + + assert.Nil(t, err) + assert.Equal(t, build.Commit.Id, "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd") +} + +func TestProjectCancelBuild(t *testing.T) { + ts, gitlab := Stub("stubs/builds/cancel.json") + defer ts.Close() + + build, err := gitlab.ProjectBuild("3", "12") + + assert.Nil(t, err) + assert.Equal(t, build.Status, "canceled") +} + +func TestProjectRetryBuild(t *testing.T) { + ts, gitlab := Stub("stubs/builds/retry.json") + defer ts.Close() + + build, err := gitlab.ProjectBuild("3", "12") + + assert.Nil(t, err) + assert.Equal(t, build.Status, "pending") +} + +func TestProjectEraseBuild(t *testing.T) { + ts, gitlab := Stub("stubs/builds/erase.json") + defer ts.Close() + + build, err := gitlab.ProjectBuild("3", "12") + + assert.Nil(t, err) + assert.Equal(t, build.Status, "failed") +} + +func TestProjectArtifact(t *testing.T) { + ts, gitlab := Stub("stubs/builds/content.txt") + defer ts.Close() + + r, err := gitlab.ProjectBuildArtifacts("3", "12") + + assert.Nil(t, err) + + defer r.Close() + + contents, err := ioutil.ReadAll(r) + + assert.Equal(t, string(contents), "a content") +} diff --git a/events.go b/events.go index 62df3ce..712275d 100644 --- a/events.go +++ b/events.go @@ -41,13 +41,13 @@ func (g *Gitlab) Activity() (ActivityFeed, error) { contents, err := g.buildAndExecRequest("GET", url, nil) if err != nil { - fmt.Println("%s", err) + fmt.Printf("%s\n", err.Error()) } var activity ActivityFeed err = xml.Unmarshal(contents, &activity) if err != nil { - fmt.Println("%s", err) + fmt.Printf("%s\n", err.Error()) } return activity, err @@ -59,13 +59,13 @@ func (g *Gitlab) RepoActivityFeed(feedPath string) ActivityFeed { contents, err := g.buildAndExecRequest("GET", url, nil) if err != nil { - fmt.Println("%s", err) + fmt.Printf("%s\n", err) } var activity ActivityFeed err = xml.Unmarshal(contents, &activity) if err != nil { - fmt.Println("%s", err) + fmt.Printf("%s\n", err) } return activity diff --git a/gitlab.go b/gitlab.go index fe6b281..3c41ed4 100644 --- a/gitlab.go +++ b/gitlab.go @@ -68,8 +68,7 @@ func (g *Gitlab) ResourceUrl(url string, params map[string]string) string { return url } -func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, error) { - +func (g *Gitlab) execRequest(method, url string, body []byte) (*http.Response, error) { var req *http.Request var err error @@ -79,6 +78,7 @@ func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, e } else { req, err = http.NewRequest(method, url, nil) } + if err != nil { panic("Error while building gitlab request") } @@ -87,16 +87,28 @@ func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, e if err != nil { return nil, fmt.Errorf("Client.Do error: %q", err) } + + if resp.StatusCode >= http.StatusBadRequest { + err = fmt.Errorf("*Gitlab.buildAndExecRequest failed: <%d> %s", resp.StatusCode, req.URL) + } + + return resp, err +} + +func (g *Gitlab) buildAndExecRequest(method, url string, body []byte) ([]byte, error) { + resp, err := g.execRequest(method, url, body) + + if err != nil { + return nil, 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 } @@ -115,6 +127,7 @@ func (g *Gitlab) ResourceUrlRaw(u string, params map[string]string) (string, str return u, "" } opaque := "//" + p.Host + g.ApiPath + path + return u, opaque } diff --git a/helper_test.go b/helper_test.go index 2bac5df..2f69784 100644 --- a/helper_test.go +++ b/helper_test.go @@ -7,10 +7,23 @@ import ( ) func Stub(filename string) (*httptest.Server, *Gitlab) { - stub, _ := ioutil.ReadFile(filename) + + var err error + + stub := []byte("") + + if len(filename) > 0 { + stub, err = ioutil.ReadFile(filename) + + if err != nil { + panic(err) + } + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(stub)) })) gitlab := NewGitlab(ts.URL, "", "") + return ts, gitlab } diff --git a/public_keys.go b/public_keys.go index de6016f..d81df97 100644 --- a/public_keys.go +++ b/public_keys.go @@ -40,7 +40,6 @@ func (g *Gitlab) ListKeys(id string) ([]*PublicKey, error) { return keys, err } - func (g *Gitlab) UserKey(id string) (*PublicKey, error) { url := g.ResourceUrl(user_key, map[string]string{":id": id}) var key *PublicKey diff --git a/stubs/builds/build.json b/stubs/builds/build.json new file mode 100644 index 0000000..1a0d299 --- /dev/null +++ b/stubs/builds/build.json @@ -0,0 +1,39 @@ +{ + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.880Z", + "download_url": null, + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:31.198Z", + "id": 8, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:30.733Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } +} \ No newline at end of file diff --git a/stubs/builds/cancel.json b/stubs/builds/cancel.json new file mode 100644 index 0000000..a9be81f --- /dev/null +++ b/stubs/builds/cancel.json @@ -0,0 +1,25 @@ +{ + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "download_url": null, + "artifacts_file": null, + "finished_at": "2016-01-11T10:14:09.526Z", + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "canceled", + "tag": false, + "user": null +} \ No newline at end of file diff --git a/stubs/builds/commit_builds_list.json b/stubs/builds/commit_builds_list.json new file mode 100644 index 0000000..9ca3083 --- /dev/null +++ b/stubs/builds/commit_builds_list.json @@ -0,0 +1,66 @@ +[ + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "download_url": null, + "artifacts_file": null, + "finished_at": "2016-01-11T10:14:09.526Z", + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "canceled", + "tag": false, + "user": null + }, + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.957Z", + "download_url": null, + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:33.913Z", + "id": 9, + "name": "brakeman", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:33.727Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } + } +] \ No newline at end of file diff --git a/stubs/builds/content.txt b/stubs/builds/content.txt new file mode 100644 index 0000000..76fc659 --- /dev/null +++ b/stubs/builds/content.txt @@ -0,0 +1 @@ +a content \ No newline at end of file diff --git a/stubs/builds/erase.json b/stubs/builds/erase.json new file mode 100644 index 0000000..bbf90ea --- /dev/null +++ b/stubs/builds/erase.json @@ -0,0 +1,24 @@ +{ + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "download_url": null, + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "created_at": "2016-01-11T10:13:33.506Z", + "started_at": "2016-01-11T10:13:33.506Z", + "finished_at": "2016-01-11T10:15:10.506Z", + "status": "failed", + "tag": false, + "user": null +} \ No newline at end of file diff --git a/stubs/builds/list.json b/stubs/builds/list.json new file mode 100644 index 0000000..6048ba4 --- /dev/null +++ b/stubs/builds/list.json @@ -0,0 +1,83 @@ +[ + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.802Z", + "download_url": null, + "artifacts_file": { + "filename": "artifacts.zip", + "size": 1000 + }, + "finished_at": "2015-12-24T17:54:27.895Z", + "id": 7, + "name": "teaspoon", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:27.722Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } + }, + { + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2015-12-24T15:51:21.727Z", + "download_url": null, + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:24.921Z", + "id": 6, + "name": "spinach:other", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": "2015-12-24T17:54:24.729Z", + "status": "failed", + "tag": false, + "user": { + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "bio": null, + "created_at": "2015-12-21T13:14:24.077Z", + "id": 1, + "is_admin": true, + "linkedin": "", + "name": "Administrator", + "skype": "", + "state": "active", + "twitter": "", + "username": "root", + "web_url": "http://gitlab.dev/u/root", + "website_url": "" + } + } +] \ No newline at end of file diff --git a/stubs/builds/retry.json b/stubs/builds/retry.json new file mode 100644 index 0000000..af95d50 --- /dev/null +++ b/stubs/builds/retry.json @@ -0,0 +1,25 @@ +{ + "commit": { + "author_email": "admin@example.com", + "author_name": "Administrator", + "created_at": "2015-12-24T16:51:14.000+01:00", + "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "message": "Test the CI integration.", + "short_id": "0ff3ae19", + "title": "Test the CI integration." + }, + "coverage": null, + "created_at": "2016-01-11T10:13:33.506Z", + "download_url": null, + "artifacts_file": null, + "finished_at": null, + "id": 69, + "name": "rubocop", + "ref": "master", + "runner": null, + "stage": "test", + "started_at": null, + "status": "pending", + "tag": false, + "user": null +} \ No newline at end of file diff --git a/users.go b/users.go index a9e160d..ffc1a97 100644 --- a/users.go +++ b/users.go @@ -6,9 +6,9 @@ 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 + user_url = "/users/:id" // Get a single user. + current_user_url = "/user" // Get current user ) type User struct { @@ -25,7 +25,8 @@ type User struct { 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"` + ColorSchemeId int `json:"color_scheme_id,omitempty"` + AvatarUrl string `json:"avatar_url,omitempty"` } func (g *Gitlab) Users(page, per_page int) ([]*User, error) {