diff options
-rw-r--r-- | changelogs/unreleased/26847-api-pipelines-use-basic.yml | 4 | ||||
-rw-r--r-- | doc/api/pipelines.md | 40 | ||||
-rw-r--r-- | doc/api/v3_to_v4.md | 1 | ||||
-rw-r--r-- | lib/api/api.rb | 1 | ||||
-rw-r--r-- | lib/api/pipelines.rb | 4 | ||||
-rw-r--r-- | lib/api/v3/pipelines.rb | 36 | ||||
-rw-r--r-- | spec/requests/api/pipelines_spec.rb | 1 | ||||
-rw-r--r-- | spec/requests/api/v3/pipelines_spec.rb | 203 |
8 files changed, 250 insertions, 40 deletions
diff --git a/changelogs/unreleased/26847-api-pipelines-use-basic.yml b/changelogs/unreleased/26847-api-pipelines-use-basic.yml new file mode 100644 index 00000000000..2034a4ba080 --- /dev/null +++ b/changelogs/unreleased/26847-api-pipelines-use-basic.yml @@ -0,0 +1,4 @@ +--- +title: Expose pipelines as PipelineBasic `api/v3/projects/:id/pipelines` +merge_request: 8875 +author: diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 9d6f3ea41d9..d809ec032f8 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -24,49 +24,13 @@ Example of response "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/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, - "coverage": "30.0" + "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a" }, { "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/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, - "coverage": null + "sha": "eb94b618fb5865b26e80fdd8ae531b7a63ad851a" } ] ``` diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 42deccba0a6..39dc6d98e7b 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -58,3 +58,4 @@ changes are in V4: - Remove `GET /groups/owned`. Use `GET /groups?owned=true` instead [!9505](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9505) - Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449) - `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096) +- Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875) diff --git a/lib/api/api.rb b/lib/api/api.rb index 3e53ab693ab..89449ce8813 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -20,6 +20,7 @@ module API mount ::API::V3::MergeRequestDiffs mount ::API::V3::MergeRequests mount ::API::V3::Notes + mount ::API::V3::Pipelines mount ::API::V3::ProjectHooks mount ::API::V3::Milestones mount ::API::V3::Projects diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 3afc1e385fe..0721b975ba4 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -10,7 +10,7 @@ module API resource :projects do desc 'Get all Pipelines of the project' do detail 'This feature was introduced in GitLab 8.11.' - success Entities::Pipeline + success Entities::PipelineBasic end params do use :pagination @@ -21,7 +21,7 @@ module API authorize! :read_pipeline, user_project pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) - present paginate(pipelines), with: Entities::Pipeline + present paginate(pipelines), with: Entities::PipelineBasic end desc 'Create a new pipeline' do diff --git a/lib/api/v3/pipelines.rb b/lib/api/v3/pipelines.rb new file mode 100644 index 00000000000..2c26a5f7d35 --- /dev/null +++ b/lib/api/v3/pipelines.rb @@ -0,0 +1,36 @@ +module API + module V3 + class Pipelines < Grape::API + include PaginationParams + + 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 ::API::Entities::Pipeline + end + params do + use :pagination + optional :scope, type: String, values: %w(running branches tags), + desc: 'Either running, branches, or tags' + end + get ':id/pipelines' do + authorize! :read_pipeline, user_project + + pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) + present paginate(pipelines), with: ::API::Entities::Pipeline + end + end + + helpers do + def pipeline + @pipeline ||= user_project.pipelines.find(params[:pipeline_id]) + end + end + end + end +end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 98d004b572e..51af999b455 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -24,6 +24,7 @@ describe API::Pipelines, api: true do 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 + expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status]) end end diff --git a/spec/requests/api/v3/pipelines_spec.rb b/spec/requests/api/v3/pipelines_spec.rb new file mode 100644 index 00000000000..3786eb06932 --- /dev/null +++ b/spec/requests/api/v3/pipelines_spec.rb @@ -0,0 +1,203 @@ +require 'spec_helper' + +describe API::V3::Pipelines, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:non_member) { create(:user) } + let(:project) { create(:project, :repository, creator: user) } + + let!(:pipeline) do + create(:ci_empty_pipeline, project: project, sha: project.commit.id, + ref: project.default_branch) + end + + before { project.team << [user, :master] } + + shared_examples 'a paginated resources' do + before do + # Fires the request + request + end + + it 'has pagination headers' do + expect(response).to include_pagination_headers + end + end + + describe 'GET /projects/:id/pipelines ' do + it_behaves_like 'a paginated resources' do + let(:request) { get v3_api("/projects/#{project.id}/pipelines", user) } + end + + context 'authorized user' do + it 'returns project pipelines' do + get v3_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 + expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status before_sha tag yaml_errors user created_at updated_at started_at finished_at committed_at duration coverage]) + end + end + + context 'unauthorized user' do + it 'does not return project pipelines' do + get v3_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 'POST /projects/:id/pipeline ' do + context 'authorized user' do + context 'with gitlab-ci.yml' do + before { stub_ci_pipeline_to_return_yaml_file } + + it 'creates and returns a new pipeline' do + expect do + post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + end.to change { Ci::Pipeline.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response).to be_a Hash + expect(json_response['sha']).to eq project.commit.id + end + + it 'fails when using an invalid ref' do + post v3_api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref' + + expect(response).to have_http_status(400) + expect(json_response['message']['base'].first).to eq 'Reference not found' + expect(json_response).not_to be_an Array + end + end + + context 'without gitlab-ci.yml' do + it 'fails to create pipeline' do + post v3_api("/projects/#{project.id}/pipeline", user), ref: project.default_branch + + expect(response).to have_http_status(400) + expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file' + expect(json_response).not_to be_an Array + end + end + end + + context 'unauthorized user' do + it 'does not create pipeline' do + post v3_api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch + + 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 v3_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 v3_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 + + context 'with coverage' do + before do + create(:ci_build, coverage: 30, pipeline: pipeline) + end + + it 'exposes the coverage' do + get v3_api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) + + expect(json_response["coverage"].to_i).to eq(30) + end + end + end + + context 'unauthorized user' do + it 'should not return a project pipeline' do + get v3_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 v3_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 v3_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 v3_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 v3_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 |