diff options
author | Douwe Maan <douwe@gitlab.com> | 2016-08-18 18:18:25 +0000 |
---|---|---|
committer | Ruben Davila <rdavila84@gmail.com> | 2016-08-18 18:45:39 -0500 |
commit | b07cd8ec641a933b819300e005e7124dd1d11c81 (patch) | |
tree | 13779374dcb2ae9a546acead7e34f75df45fd409 | |
parent | ab5cc542aac758204b9a6c8e1361e1b39c0564fe (diff) | |
download | gitlab-ce-b07cd8ec641a933b819300e005e7124dd1d11c81.tar.gz |
Merge branch 'zj-pipelines-api-endpoints' into 'master'
Add endpoints for pipelines
- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [x] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [x] API support added
- Tests
- [x] Added for this feature/bug
- [x] All builds are passing
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [ ] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
See merge request !5837
-rw-r--r-- | CHANGELOG | 1 | ||||
-rw-r--r-- | doc/api/README.md | 1 | ||||
-rw-r--r-- | doc/api/pipelines.md | 207 | ||||
-rw-r--r-- | lib/api/api.rb | 1 | ||||
-rw-r--r-- | lib/api/entities.rb | 8 | ||||
-rw-r--r-- | lib/api/pipelines.rb | 74 | ||||
-rw-r--r-- | spec/factories/ci/pipelines.rb | 18 | ||||
-rw-r--r-- | spec/requests/api/pipelines_spec.rb | 133 |
8 files changed, 425 insertions, 18 deletions
diff --git a/CHANGELOG b/CHANGELOG index 0324a5be080..71128413ff9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,7 @@ v 8.11.0 (unreleased) - Show deployment status on merge requests with external URLs - Clean up unused routes (Josef Strzibny) - Fix issue on empty project to allow developers to only push to protected branches if given permission + - API: Add enpoints for pipelines - Add green outline to New Branch button. !5447 (winniehell) - Optimize generating of cache keys for issues and notes - Fix repository push email formatting in Outlook diff --git a/doc/api/README.md b/doc/api/README.md index f3117815c7c..3e79cce0120 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -26,6 +26,7 @@ following locations: - [Open source license templates](licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) +- [Pipelines](pipelines.md) - [Projects](projects.md) including setting Webhooks - [Project Access Requests](access_requests.md) - [Project Members](members.md) diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md new file mode 100644 index 00000000000..847408a7f61 --- /dev/null +++ b/doc/api/pipelines.md @@ -0,0 +1,207 @@ +# Pipelines API + +## List project pipelines + +> [Introduced][ce-5837] in GitLab 8.11 + +``` +GET /projects/:id/pipelines +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines" +``` + +Example of response + +```json +[ + { + "id": 47, + "status": "pending", + "ref": "new-pipeline", + "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "before_sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "tag": false, + "yaml_errors": null, + "user": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2016-08-16T10:23:19.007Z", + "updated_at": "2016-08-16T10:23:19.216Z", + "started_at": null, + "finished_at": null, + "committed_at": null, + "duration": null + }, + { + "id": 48, + "status": "pending", + "ref": "new-pipeline", + "sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a", + "before_sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a", + "tag": false, + "yaml_errors": null, + "user": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2016-08-16T10:23:21.184Z", + "updated_at": "2016-08-16T10:23:21.314Z", + "started_at": null, + "finished_at": null, + "committed_at": null, + "duration": null + } +] +``` + +## Get a single pipeline + +> [Introduced][ce-5837] in GitLab 8.11 + +``` +GET /projects/:id/pipelines/:pipeline_id +``` + +| Attribute | Type | Required | Description | +|------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | +| `pipeline_id` | integer | yes | The ID of a pipeline | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipeline/46" +``` + +Example of response + +```json +{ + "id": 46, + "status": "success", + "ref": "master", + "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "before_sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "tag": false, + "yaml_errors": null, + "user": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2016-08-11T11:28:34.085Z", + "updated_at": "2016-08-11T11:32:35.169Z", + "started_at": null, + "finished_at": "2016-08-11T11:32:35.145Z", + "committed_at": null, + "duration": null +} +``` + +## Retry failed builds in a pipeline + +> [Introduced][ce-5837] in GitLab 8.11 + +``` +POST /projects/:id/pipelines/:pipeline_id/retry +``` + +| Attribute | Type | Required | Description | +|------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | +| `pipeline_id` | integer | yes | The ID of a pipeline | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines/46/retry" +``` + +Response: + +```json +{ + "id": 46, + "status": "pending", + "ref": "master", + "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "before_sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "tag": false, + "yaml_errors": null, + "user": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2016-08-11T11:28:34.085Z", + "updated_at": "2016-08-11T11:32:35.169Z", + "started_at": null, + "finished_at": "2016-08-11T11:32:35.145Z", + "committed_at": null, + "duration": null +} +``` + +## Cancel a pipelines builds + +> [Introduced][ce-5837] in GitLab 8.11 + +``` +POST /projects/:id/pipelines/:pipeline_id/cancel +``` + +| Attribute | Type | Required | Description | +|------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | +| `pipeline_id` | integer | yes | The ID of a pipeline | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/pipelines/46/cancel" +``` + +Response: + +```json +{ + "id": 46, + "status": "canceled", + "ref": "master", + "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "before_sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a", + "tag": false, + "yaml_errors": null, + "user": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2016-08-11T11:28:34.085Z", + "updated_at": "2016-08-11T11:32:35.169Z", + "started_at": null, + "finished_at": "2016-08-11T11:32:35.145Z", + "committed_at": null, + "duration": null +} +``` + +[ce-5837]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5837 diff --git a/lib/api/api.rb b/lib/api/api.rb index d43af3f24e9..d9261c97908 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -56,6 +56,7 @@ module API mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes + mount ::API::Pipelines mount ::API::ProjectHooks mount ::API::ProjectSnippets mount ::API::Projects diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ec455e67329..99eb8af5ab7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -500,6 +500,14 @@ module API expose :key, :value end + class Pipeline < Grape::Entity + expose :id, :status, :ref, :sha, :before_sha, :tag, :yaml_errors + + expose :user, with: Entities::UserBasic + expose :created_at, :updated_at, :started_at, :finished_at, :committed_at + expose :duration + end + class Environment < Grape::Entity expose :id, :name, :external_url end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb new file mode 100644 index 00000000000..2aae75c471d --- /dev/null +++ b/lib/api/pipelines.rb @@ -0,0 +1,74 @@ +module API + class Pipelines < Grape::API + before { authenticate! } + + params do + requires :id, type: String, desc: 'The project ID' + end + resource :projects do + desc 'Get all Pipelines of the project' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + optional :page, type: Integer, desc: 'Page number of the current request' + optional :per_page, type: Integer, desc: 'Number of items per page' + end + get ':id/pipelines' do + authorize! :read_pipeline, user_project + + present paginate(user_project.pipelines), with: Entities::Pipeline + end + + desc 'Gets a specific pipeline for the project' do + detail 'This feature was introduced in GitLab 8.11' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + get ':id/pipelines/:pipeline_id' do + authorize! :read_pipeline, user_project + + present pipeline, with: Entities::Pipeline + end + + desc 'Retry failed builds in the pipeline' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + post ':id/pipelines/:pipeline_id/retry' do + authorize! :update_pipeline, user_project + + pipeline.retry_failed(current_user) + + present pipeline, with: Entities::Pipeline + end + + desc 'Cancel all builds in the pipeline' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + post ':id/pipelines/:pipeline_id/cancel' do + authorize! :update_pipeline, user_project + + pipeline.cancel_running + + status 200 + present pipeline.reload, with: Entities::Pipeline + end + end + + helpers do + def pipeline + @pipeline ||= user_project.pipelines.find(params[:pipeline_id]) + end + end + end +end diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 04d66020c87..ac2a1ba5dff 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: commits -# -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime -# gl_project_id :integer -# - FactoryGirl.define do factory :ci_empty_pipeline, class: Ci::Pipeline do ref 'master' diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb new file mode 100644 index 00000000000..7011bdc9ec0 --- /dev/null +++ b/spec/requests/api/pipelines_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:non_member) { create(:user) } + let(:project) { create(:project, creator_id: user.id) } + + let!(:pipeline) do + create(:ci_empty_pipeline, project: project, sha: project.commit.id, + ref: project.default_branch) + end + + before { project.team << [user, :master] } + + describe 'GET /projects/:id/pipelines ' do + it_behaves_like 'a paginated resources' do + let(:request) { get api("/projects/#{project.id}/pipelines", user) } + end + + context 'authorized user' do + it 'returns project pipelines' do + get api("/projects/#{project.id}/pipelines", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['sha']).to match /\A\h{40}\z/ + expect(json_response.first['id']).to eq pipeline.id + end + end + + context 'unauthorized user' do + it 'does not return project pipelines' do + get api("/projects/#{project.id}/pipelines", non_member) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + expect(json_response).not_to be_an Array + end + end + end + + describe 'GET /projects/:id/pipelines/:pipeline_id' do + context 'authorized user' do + it 'returns project pipelines' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['sha']).to match /\A\h{40}\z/ + end + + it 'returns 404 when it does not exist' do + get api("/projects/#{project.id}/pipelines/123456", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq '404 Not found' + expect(json_response['id']).to be nil + end + end + + context 'unauthorized user' do + it 'should not return a project pipeline' do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}", non_member) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + expect(json_response['id']).to be nil + end + end + end + + describe 'POST /projects/:id/pipelines/:pipeline_id/retry' do + context 'authorized user' do + let!(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit.id, + ref: project.default_branch) + end + + let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } + + it 'retries failed builds' do + expect do + post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", user) + end.to change { pipeline.builds.count }.from(1).to(2) + + expect(response).to have_http_status(201) + expect(build.reload.retried?).to be true + end + end + + context 'unauthorized user' do + it 'should not return a project pipeline' do + post api("/projects/#{project.id}/pipelines/#{pipeline.id}/retry", non_member) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq '404 Project Not Found' + expect(json_response['id']).to be nil + end + end + end + + describe 'POST /projects/:id/pipelines/:pipeline_id/cancel' do + let!(:pipeline) do + create(:ci_empty_pipeline, project: project, sha: project.commit.id, + ref: project.default_branch) + end + + let!(:build) { create(:ci_build, :running, pipeline: pipeline) } + + context 'authorized user' do + it 'retries failed builds' do + post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", user) + + expect(response).to have_http_status(200) + expect(json_response['status']).to eq('canceled') + end + end + + context 'user without proper access rights' do + let!(:reporter) { create(:user) } + + before { project.team << [reporter, :reporter] } + + it 'rejects the action' do + post api("/projects/#{project.id}/pipelines/#{pipeline.id}/cancel", reporter) + + expect(response).to have_http_status(403) + expect(pipeline.reload.status).to eq('pending') + end + end + end +end |