diff options
-rw-r--r-- | changelogs/unreleased/tc-api-pipeline-jobs.yml | 4 | ||||
-rw-r--r-- | doc/api/jobs.md | 120 | ||||
-rw-r--r-- | lib/api/jobs.rb | 39 | ||||
-rw-r--r-- | lib/api/v3/builds.rb | 24 | ||||
-rw-r--r-- | spec/requests/api/jobs_spec.rb | 76 |
5 files changed, 229 insertions, 34 deletions
diff --git a/changelogs/unreleased/tc-api-pipeline-jobs.yml b/changelogs/unreleased/tc-api-pipeline-jobs.yml new file mode 100644 index 00000000000..993c1b6526a --- /dev/null +++ b/changelogs/unreleased/tc-api-pipeline-jobs.yml @@ -0,0 +1,4 @@ +--- +title: Add GET /projects/:id/pipelines/:pipeline_id/jobs endpoint +merge_request: 9727 +author: diff --git a/doc/api/jobs.md b/doc/api/jobs.md index 296f1d025dd..7340123e09d 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -1,6 +1,6 @@ # Jobs API -## List project jobs +## List project jobs Get a list of jobs in a project. @@ -14,7 +14,123 @@ GET /projects/:id/jobs | `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope%5B0%5D=pending&scope%5B1%5D=running' +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running' +``` + +Example of response + +```json +[ + { + "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", + "artifacts_file": { + "filename": "artifacts.zip", + "size": 1000 + }, + "finished_at": "2015-12-24T17:54:27.895Z", + "id": 7, + "name": "teaspoon", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + }, + "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/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", + "artifacts_file": null, + "finished_at": "2015-12-24T17:54:24.921Z", + "id": 6, + "name": "spinach:other", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + }, + "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/root", + "website_url": "" + } + } +] +``` + +## List pipeline jobs + +Get a list of jobs for a pipeline. + +``` +GET /projects/:id/pipeline/:pipeline_id/jobs +``` + +| Attribute | Type | Required | Description | +|---------------|--------------------------------|----------|----------------------| +| `id` | integer | yes | The ID of a project | +| `pipeline_id` | integer | yes | The ID of a pipeline | +| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided | + +``` +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running' ``` Example of response diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 33c05e8aa63..44118522abe 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -18,6 +18,8 @@ module API [scope] when Hashie::Mash scope.values + when Hashie::Array + scope else ['unknown'] end @@ -36,8 +38,23 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) - present paginate(builds), with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present paginate(builds), with: Entities::Job + end + + desc 'Get pipeline jobs' do + success Entities::Job + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + use :optional_scope + use :pagination + end + get ':id/pipelines/:pipeline_id/jobs' do + pipeline = user_project.pipelines.find(params[:pipeline_id]) + builds = pipeline.builds + builds = filter_builds(builds, params[:scope]) + + present paginate(builds), with: Entities::Job end desc 'Get a specific job of a project' do @@ -51,8 +68,7 @@ module API build = get_build!(params[:job_id]) - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: Entities::Job end desc 'Download the artifacts file from a job' do @@ -119,8 +135,7 @@ module API build.cancel - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: Entities::Job end desc 'Retry a specific build of a project' do @@ -137,8 +152,7 @@ module API build = Ci::Build.retry(build, current_user) - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: Entities::Job end desc 'Erase job (remove artifacts and the trace)' do @@ -154,8 +168,7 @@ module API return forbidden!('Job is not erasable!') unless build.erasable? build.erase(erased_by: current_user) - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project) + present build, with: Entities::Job end desc 'Keep the artifacts to prevent them from being deleted' do @@ -173,8 +186,7 @@ module API build.keep_artifacts! status 200 - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: Entities::Job end desc 'Trigger a manual job' do @@ -194,8 +206,7 @@ module API build.play(current_user) status 200 - present build, with: Entities::Job, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: Entities::Job end end diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb index c8feba13527..6f97102c6ef 100644 --- a/lib/api/v3/builds.rb +++ b/lib/api/v3/builds.rb @@ -36,8 +36,7 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) - present paginate(builds), with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present paginate(builds), with: ::API::V3::Entities::Build end desc 'Get builds for a specific commit of a project' do @@ -57,8 +56,7 @@ module API builds = user_project.builds.where(pipeline: pipelines).order('id DESC') builds = filter_builds(builds, params[:scope]) - present paginate(builds), with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present paginate(builds), with: ::API::V3::Entities::Build end desc 'Get a specific build of a project' do @@ -72,8 +70,7 @@ module API build = get_build!(params[:build_id]) - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: ::API::V3::Entities::Build end desc 'Download the artifacts file from build' do @@ -140,8 +137,7 @@ module API build.cancel - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: ::API::V3::Entities::Build end desc 'Retry a specific build of a project' do @@ -158,8 +154,7 @@ module API build = Ci::Build.retry(build, current_user) - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: ::API::V3::Entities::Build end desc 'Erase build (remove artifacts and build trace)' do @@ -175,8 +170,7 @@ module API return forbidden!('Build is not erasable!') unless build.erasable? build.erase(erased_by: current_user) - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project) + present build, with: ::API::V3::Entities::Build end desc 'Keep the artifacts to prevent them from being deleted' do @@ -194,8 +188,7 @@ module API build.keep_artifacts! status 200 - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: ::API::V3::Entities::Build end desc 'Trigger a manual build' do @@ -215,8 +208,7 @@ module API build.play(current_user) status 200 - present build, with: ::API::V3::Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) + present build, with: ::API::V3::Entities::Build end end diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index a4d27734cc2..9450701064b 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -51,7 +51,7 @@ describe API::Jobs, api: true do end context 'filter project with array of scope elements' do - let(:query) { { 'scope[0]' => 'pending', 'scope[1]' => 'running' } } + let(:query) { { scope: %w(pending running) } } it do expect(response).to have_http_status(200) @@ -60,7 +60,7 @@ describe API::Jobs, api: true do end context 'respond 400 when scope contains invalid state' do - let(:query) { { 'scope[0]' => 'unknown', 'scope[1]' => 'running' } } + let(:query) { { scope: %w(unknown running) } } it { expect(response).to have_http_status(400) } end @@ -75,6 +75,78 @@ describe API::Jobs, api: true do end end + describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do + let(:query) { Hash.new } + + before do + get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query + end + + context 'authorized user' do + it 'returns pipeline jobs' do + expect(response).to have_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + end + + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + end + + it 'returns pipeline data' do + json_build = json_response.first + + expect(json_build['pipeline']).not_to be_empty + expect(json_build['pipeline']['id']).to eq build.pipeline.id + expect(json_build['pipeline']['ref']).to eq build.pipeline.ref + expect(json_build['pipeline']['sha']).to eq build.pipeline.sha + expect(json_build['pipeline']['status']).to eq build.pipeline.status + end + + context 'filter jobs with one scope element' do + let(:query) { { 'scope' => 'pending' } } + + it do + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + end + end + + context 'filter jobs with array of scope elements' do + let(:query) { { scope: %w(pending running) } } + + it do + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + end + end + + context 'respond 400 when scope contains invalid state' do + let(:query) { { scope: %w(unknown running) } } + + it { expect(response).to have_http_status(400) } + end + + context 'jobs in different pipelines' do + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + let!(:build2) { create(:ci_build, pipeline: pipeline2) } + + it 'excludes jobs from other pipelines' do + json_response.each { |job| expect(job['pipeline']['id']).to eq(pipeline.id) } + end + end + end + + context 'unauthorized user' do + let(:api_user) { nil } + + it 'does not return jobs' do + expect(response).to have_http_status(401) + end + end + end + describe 'GET /projects/:id/jobs/:job_id' do before do get api("/projects/#{project.id}/jobs/#{build.id}", api_user) |