Commit 4ddd7222 authored by Douwe Maan's avatar Douwe Maan

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
parents fd07cb63 df5661b6
......@@ -52,6 +52,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
......
......@@ -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)
......
# 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
......@@ -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
......
......@@ -502,6 +502,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
......
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
# == 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'
......
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment