From 128be3c0103f601b2c80f3489646e57e202f6327 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 26 Jan 2016 17:03:38 +0100 Subject: [PATCH 01/36] Add basic runners management API - add feature to list runners - add feature to show runners details - add feature to delete runner - add feature to update runner --- app/models/ci/runner.rb | 6 + lib/api/api.rb | 1 + lib/api/entities.rb | 6 + lib/api/runners.rb | 107 ++++++++++++ spec/requests/api/runners_spec.rb | 278 ++++++++++++++++++++++++++++++ 5 files changed, 398 insertions(+) create mode 100644 lib/api/runners.rb create mode 100644 spec/requests/api/runners_spec.rb diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 38b20cd7faa4..1e914b444998 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -22,6 +22,7 @@ class Runner < ActiveRecord::Base extend Ci::Model LAST_CONTACT_TIME = 5.minutes.ago + AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online'] has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' @@ -38,6 +39,11 @@ class Runner < ActiveRecord::Base scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } scope :ordered, ->() { order(id: :desc) } + scope :owned_or_shared, ->(project_id) do + joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id') + .where("ci_runner_projects.gl_project_id = #{project_id} OR ci_runners.is_shared = true") + end + acts_as_taggable def self.search(query) diff --git a/lib/api/api.rb b/lib/api/api.rb index 7efe0a0262f7..7d65145176b9 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -56,5 +56,6 @@ class API < Grape::API mount Triggers mount Builds mount Variables + mount Runners end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a9c09ffdb311..bdbf2cf50821 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -377,6 +377,12 @@ class Runner < Grape::Entity expose :name end + class RunnerDetails < Runner + expose :tag_list + expose :version, :revision, :platform, :architecture + expose :contacted_at, as: :last_contact + end + class Build < Grape::Entity expose :id, :status, :stage, :name, :ref, :tag, :coverage expose :created_at, :started_at, :finished_at diff --git a/lib/api/runners.rb b/lib/api/runners.rb new file mode 100644 index 000000000000..7d4ca3f1587e --- /dev/null +++ b/lib/api/runners.rb @@ -0,0 +1,107 @@ +module API + # Runners API + class Runners < Grape::API + before { authenticate! } + + resource :runners do + # Get available shared runners + # + # Example Request: + # GET /runners + get do + runners = + if current_user.is_admin? + Ci::Runner.all + else + current_user.ci_authorized_runners + end + + runners = filter_runners(runners, params[:scope]) + present paginate(runners), with: Entities::Runner + end + + get ':id' do + runner = get_runner(params[:id]) + can_show_runner?(runner) unless current_user.is_admin? + + present runner, with: Entities::RunnerDetails + end + + put ':id' do + runner = get_runner(params[:id]) + can_update_runner?(runner) unless current_user.is_admin? + + attrs = attributes_for_keys [:description, :active, :tag_list] + if runner.update(attrs) + present runner, with: Entities::RunnerDetails + else + render_validation_error!(runner) + end + end + + delete ':id' do + runner = get_runner(params[:id]) + can_delete_runner?(runner) + runner.destroy! + + present runner, with: Entities::RunnerDetails + end + end + + resource :projects do + before { authorize_admin_project } + + # Get runners available for project + # + # Example Request: + # GET /projects/:id/runners + get ':id/runners' do + runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope]) + present paginate(runners), with: Entities::Runner + end + end + + helpers do + def filter_runners(runners, scope) + return runners unless scope.present? + + available_scopes = ::Ci::Runner::AVAILABLE_SCOPES + unless (available_scopes && scope).empty? + runners.send(scope) + else + render_api_error!('Scope contains invalid value', 400) + end + end + + def get_runner(id) + runner = Ci::Runner.find(id) + not_found!('Runner') unless runner + runner + end + + def can_show_runner?(runner) + return true if runner.is_shared + forbidden!("Can't show runner's details - no access granted") unless user_can_access_runner?(runner) + end + + def can_update_runner?(runner) + return true if current_user.is_admin? + forbidden!("Can't update shared runner") if runner.is_shared? + forbidden!("Can't update runner - no access granted") unless user_can_access_runner?(runner) + end + + def can_delete_runner?(runner) + return true if current_user.is_admin? + forbidden!("Can't delete shared runner") if runner.is_shared? + forbidden!("Can't delete runner - associated with more than one project") if runner.projects.count > 1 + forbidden!("Can't delete runner - no access granted") unless user_can_access_runner?(runner) + end + + def user_can_access_runner?(runner) + runner.projects.inject(false) do |final, project| + final ||= abilities.allowed?(current_user, :admin_project, project) + end + end + end + end +end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb new file mode 100644 index 000000000000..e5c837d051b2 --- /dev/null +++ b/spec/requests/api/runners_spec.rb @@ -0,0 +1,278 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:admin) { create(:user, :admin) } + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:project2) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) } + let!(:shared_runner) { create(:ci_shared_runner, tag_list: ['mysql', 'ruby'], active: true) } + let!(:specific_runner) { create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) } + let!(:specific_runner_project) { create(:ci_runner_project, runner: specific_runner, project: project) } + let!(:specific_runner2) { create(:ci_specific_runner) } + let!(:specific_runner2_project) { create(:ci_runner_project, runner: specific_runner2, project: project2) } + let!(:unused_specific_runner) { create(:ci_specific_runner) } + let!(:two_projects_runner) { create(:ci_specific_runner) } + let!(:two_projects_runner_project) { create(:ci_runner_project, runner: two_projects_runner, project: project) } + let!(:two_projects_runner_project2) { create(:ci_runner_project, runner: two_projects_runner, project: project2) } + + describe 'GET /runners' do + context 'authorized user with admin privileges' do + it 'should return all runners' do + get api('/runners', admin) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_truthy + end + + it 'should filter runners by scope' do + get api('/runners?scope=specific', admin) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_falsey + end + end + + context 'authorized user without admin privileges' do + it 'should return user available runners' do + get api('/runners', user) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_falsey + end + end + + context 'unauthorized user' do + it 'should not return runners' do + get api('/runners') + + expect(response.status).to eq(401) + end + end + end + + describe 'GET /runners/:id' do + context 'admin user' do + it "should return runner's details" do + get api("/runners/#{specific_runner.id}", admin) + + expect(response.status).to eq(200) + expect(json_response['description']).to eq(specific_runner.description) + end + + it "should return shared runner's details" do + get api("/runners/#{shared_runner.id}", admin) + + expect(response.status).to eq(200) + expect(json_response['description']).to eq(shared_runner.description) + end + + it 'should return 404 if runner does not exists' do + get api('/runners/9999', admin) + + expect(response.status).to eq(404) + end + end + + context "runner project's administrative user" do + it "should return runner's details" do + get api("/runners/#{specific_runner.id}", user) + + expect(response.status).to eq(200) + expect(json_response['description']).to eq(specific_runner.description) + end + + it "should return shared runner's details" do + get api("/runners/#{shared_runner.id}", user) + + expect(response.status).to eq(200) + expect(json_response['description']).to eq(shared_runner.description) + end + end + + context 'other authorized user' do + it "should not return runner's details" do + get api("/runners/#{specific_runner.id}", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it "should not return runner's details" do + get api("/runners/#{specific_runner.id}") + + expect(response.status).to eq(401) + end + end + end + + describe 'PUT /runners/:id' do + context 'admin user' do + it 'should update shared runner' do + description = shared_runner.description + active = shared_runner.active + tag_list = shared_runner.tag_list + put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active, + tag_list: ['ruby2.1', 'pgsql', 'mysql'] + shared_runner.reload + + expect(response.status).to eq(200) + expect(shared_runner.description).not_to eq(description) + expect(shared_runner.active).not_to eq(active) + expect(shared_runner.tag_list).not_to eq(tag_list) + end + + it 'should update specific runner' do + description = specific_runner.description + put api("/runners/#{specific_runner.id}", admin), description: 'test' + specific_runner.reload + + expect(response.status).to eq(200) + expect(specific_runner.description).to eq('test') + expect(specific_runner.description).not_to eq(description) + end + + it 'should return 404 if runner does not exists' do + put api('/runners/9999', admin), description: 'test' + + expect(response.status).to eq(404) + end + end + + context 'authorized user' do + it 'should not update shared runner' do + put api("/runners/#{shared_runner.id}", user), description: 'test' + + expect(response.status).to eq(403) + end + + it 'should not update specific runner without access to' do + put api("/runners/#{specific_runner.id}", user2), description: 'test' + + expect(response.status).to eq(403) + end + + it 'should update specific runner' do + description = specific_runner.description + put api("/runners/#{specific_runner.id}", admin), description: 'test' + specific_runner.reload + + expect(response.status).to eq(200) + expect(specific_runner.description).to eq('test') + expect(specific_runner.description).not_to eq(description) + end + + end + + context 'unauthorized user' do + it 'should not delete runner' do + put api("/runners/#{specific_runner.id}"), description: 'test' + + expect(response.status).to eq(401) + end + end + end + + describe 'DELETE /runners/:id' do + context 'admin user' do + it 'should delete shared runner' do + expect do + delete api("/runners/#{shared_runner.id}", admin) + end.to change{ Ci::Runner.shared.count }.by(-1) + expect(response.status).to eq(200) + end + + it 'should delete unused specific runner' do + expect do + delete api("/runners/#{unused_specific_runner.id}", admin) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end + + it 'should delete used specific runner' do + expect do + delete api("/runners/#{specific_runner.id}", admin) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end + + it 'should return 404 if runner does not exists' do + delete api('/runners/9999', admin) + + expect(response.status).to eq(404) + end + end + + context 'authorized user' do + it 'should not delete shared runner' do + delete api("/runners/#{shared_runner.id}", user) + expect(response.status).to eq(403) + end + + it 'should not delete runner without access to' do + delete api("/runners/#{specific_runner.id}", user2) + expect(response.status).to eq(403) + end + + it 'should not delete runner with more than one associated project' do + delete api("/runners/#{two_projects_runner.id}", user) + expect(response.status).to eq(403) + end + + it 'should delete runner for one owned project' do + expect do + delete api("/runners/#{specific_runner.id}", user) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end + end + + context 'unauthorized user' do + it 'should not delete runner' do + delete api("/runners/#{specific_runner.id}") + + expect(response.status).to eq(401) + end + end + end + + describe 'GET /projects/:id/runners' do + context 'authorized user with master privileges' do + it "should return project's runners" do + get api("/projects/#{project.id}/runners", user) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_truthy + end + end + + context 'authorized user without master privileges' do + it "should not return project's runners" do + get api("/projects/#{project.id}/runners", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it "should not return project's runners" do + get api("/projects/#{project.id}/runners") + + expect(response.status).to eq(401) + end + end + end +end -- GitLab From d42ced44d9f2ddeae5a76d60273a59d7253ca2b2 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 29 Jan 2016 15:40:25 +0100 Subject: [PATCH 02/36] Add feature to enable/disable runner in project --- lib/api/runners.rb | 26 ++++++++ spec/requests/api/runners_spec.rb | 102 ++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 7d4ca3f1587e..87b37c05397d 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -59,6 +59,26 @@ class Runners < Grape::API runners = filter_runners(Ci::Runner.owned_or_shared(user_project.id), params[:scope]) present paginate(runners), with: Entities::Runner end + + put ':id/runners/:runner_id' do + runner = get_runner(params[:runner_id]) + can_enable_runner?(runner) + Ci::RunnerProject.create(runner: runner, project: user_project) + + present runner, with: Entities::Runner + end + + delete ':id/runners/:runner_id' do + runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id]) + not_found!('Runner') unless runner_project + + runner = runner_project.runner + forbidden!("Can't disable runner - only one project associated with it. Please remove runner instead") if runner.projects.count == 1 + + runner_project.destroy + + present runner, with: Entities::Runner + end end helpers do @@ -97,6 +117,12 @@ def can_delete_runner?(runner) forbidden!("Can't delete runner - no access granted") unless user_can_access_runner?(runner) end + def can_enable_runner?(runner) + forbidden!("Can't enable shared runner directly") if runner.is_shared? + return true if current_user.is_admin? + forbidden!("Can't update runner - no access granted") unless user_can_access_runner?(runner) + end + def user_can_access_runner?(runner) runner.projects.inject(false) do |final, project| final ||= abilities.allowed?(current_user, :admin_project, project) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index e5c837d051b2..9ffa59dac070 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -9,6 +9,7 @@ let!(:project) { create(:project, creator_id: user.id) } let!(:project2) { create(:project, creator_id: user.id) } let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:master2) { create(:project_member, user: user, project: project2, access_level: ProjectMember::MASTER) } let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) } let!(:shared_runner) { create(:ci_shared_runner, tag_list: ['mysql', 'ruby'], active: true) } let!(:specific_runner) { create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) } @@ -275,4 +276,105 @@ end end end + + describe 'PUT /projects/:id/runners/:runner_id' do + context 'authorized user' do + it 'should enable specific runner' do + expect do + put api("/projects/#{project.id}/runners/#{specific_runner2.id}", user) + end.to change{ project.runners.count }.by(+1) + expect(response.status).to eq(200) + end + + it 'should avoid changes when enabling already enabled runner' do + expect do + put api("/projects/#{project.id}/runners/#{specific_runner.id}", user) + end.to change{ project.runners.count }.by(0) + expect(response.status).to eq(200) + end + + it 'should not enable shared runner' do + put api("/projects/#{project.id}/runners/#{shared_runner.id}", user) + + expect(response.status).to eq(403) + end + + context 'user is admin' do + it 'should enable any specific runner' do + expect do + put api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", admin) + end.to change{ project.runners.count }.by(+1) + expect(response.status).to eq(200) + end + end + + context 'user is not admin' do + it 'should not enable runner without access to' do + put api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", user) + + expect(response.status).to eq(403) + end + end + end + + context 'authorized user without permissions' do + it 'should not enable runner' do + put api("/projects/#{project.id}/runners/#{specific_runner2.id}", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it 'should not enable runner' do + put api("/projects/#{project.id}/runners/#{specific_runner2.id}") + + expect(response.status).to eq(401) + end + end + end + + describe 'DELETE /projects/:id/runners/:runner_id' do + context 'authorized user' do + context 'when runner have more than one associated projects' do + it "should disable project's runner" do + expect do + delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user) + end.to change{ project.runners.count }.by(-1) + expect(response.status).to eq(200) + end + end + + context 'when runner have one associated projects' do + it "should not disable project's runner" do + expect do + delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user) + end.to change{ project.runners.count }.by(0) + expect(response.status).to eq(403) + end + end + + it 'should return 404 is runner is not found' do + delete api("/projects/#{project.id}/runners/9999", user) + + expect(response.status).to eq(404) + end + end + + context 'authorized user without permissions' do + it "should not disable project's runner" do + delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2) + + expect(response.status).to eq(403) + end + end + + context 'unauthorized user' do + it "should not disable project's runner" do + delete api("/projects/#{project.id}/runners/#{specific_runner.id}") + + expect(response.status).to eq(401) + end + end + end end -- GitLab From 8c37f0ff3093995566f7f24788f8362f481b56b6 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 29 Jan 2016 16:11:48 +0100 Subject: [PATCH 03/36] Add missing methods documentation; fix rubocop reported violation --- lib/api/runners.rb | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 87b37c05397d..b7294258c7db 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -20,6 +20,12 @@ class Runners < Grape::API present paginate(runners), with: Entities::Runner end + # Get runner's details + # + # Parameters: + # id (required) - The ID of ther runner + # Example Request: + # GET /runners/:id get ':id' do runner = get_runner(params[:id]) can_show_runner?(runner) unless current_user.is_admin? @@ -27,6 +33,15 @@ class Runners < Grape::API present runner, with: Entities::RunnerDetails end + # Update runner's details + # + # Parameters: + # id (required) - The ID of ther runner + # description (optional) - Runner's description + # active (optional) - Runner's status + # tag_list (optional) - Array of tags for runner + # Example Request: + # PUT /runners/:id put ':id' do runner = get_runner(params[:id]) can_update_runner?(runner) unless current_user.is_admin? @@ -39,6 +54,12 @@ class Runners < Grape::API end end + # Remove runner + # + # Parameters: + # id (required) - The ID of ther runner + # Example Request: + # DELETE /runners/:id delete ':id' do runner = get_runner(params[:id]) can_delete_runner?(runner) @@ -60,6 +81,13 @@ class Runners < Grape::API present paginate(runners), with: Entities::Runner end + # Enable runner for project + # + # Parameters: + # id (required) - The ID of the project + # runner_id (required) - The ID of the runner + # Example Request: + # PUT /projects/:id/runners/:runner_id put ':id/runners/:runner_id' do runner = get_runner(params[:runner_id]) can_enable_runner?(runner) @@ -68,6 +96,13 @@ class Runners < Grape::API present runner, with: Entities::Runner end + # Disable project's runner + # + # Parameters: + # id (required) - The ID of the project + # runner_id (required) - The ID of the runner + # Example Request: + # DELETE /projects/:id/runners/:runner_id delete ':id/runners/:runner_id' do runner_project = user_project.runner_projects.find_by(runner_id: params[:runner_id]) not_found!('Runner') unless runner_project @@ -125,7 +160,7 @@ def can_enable_runner?(runner) def user_can_access_runner?(runner) runner.projects.inject(false) do |final, project| - final ||= abilities.allowed?(current_user, :admin_project, project) + final || abilities.allowed?(current_user, :admin_project, project) end end end -- GitLab From 53f775ae6d6d0cd33a6a1421e736670b45e59309 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 29 Jan 2016 16:44:29 +0100 Subject: [PATCH 04/36] Fix runners filtering in API --- lib/api/runners.rb | 6 ++--- spec/requests/api/runners_spec.rb | 41 ++++++++++++++++++------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index b7294258c7db..799c10a18973 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -121,11 +121,11 @@ def filter_runners(runners, scope) return runners unless scope.present? available_scopes = ::Ci::Runner::AVAILABLE_SCOPES - unless (available_scopes && scope).empty? - runners.send(scope) - else + if (available_scopes & [scope]).empty? render_api_error!('Scope contains invalid value', 400) end + + runners.send(scope) end def get_runner(id) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 9ffa59dac070..d600b2312c5c 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -22,35 +22,42 @@ let!(:two_projects_runner_project2) { create(:ci_runner_project, runner: two_projects_runner, project: project2) } describe 'GET /runners' do - context 'authorized user with admin privileges' do - it 'should return all runners' do - get api('/runners', admin) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + context 'authorized user' do + context 'authorized user with admin privileges' do + it 'should return all runners' do + get api('/runners', admin) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(shared).to be_truthy + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_truthy + end end - it 'should filter runners by scope' do - get api('/runners?scope=specific', admin) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + context 'authorized user without admin privileges' do + it 'should return user available runners' do + get api('/runners', user) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(shared).to be_falsey + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_falsey + end end - end - context 'authorized user without admin privileges' do - it 'should return user available runners' do - get api('/runners', user) + it 'should filter runners by scope' do + get api('/runners?scope=specific', user) shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array expect(shared).to be_falsey end + + it 'should avoid filtering if scope is invalid' do + get api('/runners?scope=unknown', user) + expect(response.status).to eq(400) + end end context 'unauthorized user' do -- GitLab From 43c8daf3dcdc2b592c3ad76f4eae6bd412f05630 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 1 Feb 2016 14:31:49 +0100 Subject: [PATCH 05/36] Add runners API documentation [ci-skip] --- doc/api/README.md | 1 + doc/api/runners.md | 234 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 doc/api/runners.md diff --git a/doc/api/README.md b/doc/api/README.md index 9f3ad1263201..7629ef294ac9 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -32,6 +32,7 @@ following locations: - [Builds](builds.md) - [Build triggers](build_triggers.md) - [Build Variables](build_variables.md) +- [Runners](runners.md) ## Authentication diff --git a/doc/api/runners.md b/doc/api/runners.md new file mode 100644 index 000000000000..e0e3d35535ce --- /dev/null +++ b/doc/api/runners.md @@ -0,0 +1,234 @@ +# Runners API + +## List runners + +Get a list of runners. + +``` +GET /runners +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|---------------------| +| `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | + +``` +curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" +``` + +```json +[ + { + "active": true, + "description": "test-1-20150125", + "id": 6, + "is_shared": false, + "name": null + }, + { + "active": true, + "description": "test-2-20150125", + "id": 8, + "is_shared": false, + "name": null + } +] +``` + +## Get runner's details + +Get details of a runner. + +``` +GET /runners/:id +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a runner | + +``` +curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +``` + +```json +{ + "active": true, + "architecture": null, + "description": "test-1-20150125", + "id": 6, + "is_shared": false, + "last_contact": "2016-01-25T16:39:48.066Z", + "name": null, + "platform": null, + "revision": null, + "tag_list": [ + "ruby", + "mysql" + ], + "version": null +} +``` + +## Update runner's details + +Update details of a runner. + +``` +PUT /runners/:id +``` + +| Attribute | Type | required | Description | +|---------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a runner | +| `description` | string | no | The description of a runner | +| `active` | boolean | no | The state of a runner; can be set to `true` or `false` | +| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | + +``` +curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2" +``` + +```json +{ + "active": true, + "architecture": null, + "description": "test-1-20150125-test", + "id": 6, + "is_shared": false, + "last_contact": "2016-01-25T16:39:48.066Z", + "name": null, + "platform": null, + "revision": null, + "tag_list": [ + "ruby", + "mysql", + "tag1", + "tag2" + ], + "version": null +} +``` + +## Remove a runner + +Remove a runner. + +``` +DELETE /runners/:id +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a runner | + +``` +curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +``` + +```json +{ + "active": true, + "architecture": null, + "description": "test-1-20150125-test", + "id": 6, + "is_shared": false, + "last_contact": "2016-01-25T16:39:48.066Z", + "name": null, + "platform": null, + "revision": null, + "tag_list": [], + "version": null +} +``` + +## List project's runners + +List all runners (*shared* and *specific*) available in project. Shared runners are listed if at least one shared runner +is defined **and** shared runners usage is enabled in project's settings. + +``` +GET /projects/:id/runners +``` + +| Attribute | Type | required | Description | +|-----------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | + +``` +curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" +``` + +```json +[ + { + "active": true, + "description": "test-2-20150125", + "id": 8, + "is_shared": false, + "name": null + }, + { + "active": true, + "description": "development_runner", + "id": 5, + "is_shared": true, + "name": null + } +] +``` + +## Enable a runner in project + +Enable available specific runner in project. + +``` +PUT /projects/:id/runners/:runner_id +``` + +| Attribute | Type | required | Description | +|-------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | +| `runner_id` | integer | yes | The ID of a runner | + +``` +curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +``` + +```json +{ + "active": true, + "description": "test-2016-02-01", + "id": 9, + "is_shared": false, + "name": null +} +``` + +## Disable a runner from project + +Disable a specific runner from project. It works only, if the project isn't an only project associated with the +specified runner. If so, then an error is returned and user should use the [remove a runner](#remove-a-runner) feature. + +``` +PUT /projects/:id/runners/:runner_id +``` + +| Attribute | Type | required | Description | +|-------------|---------|----------|---------------------| +| `id` | integer | yes | The ID of a project | +| `runner_id` | integer | yes | The ID of a runner | + +``` +curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +``` + +```json +{ + "active": true, + "description": "test-2016-02-01", + "id": 9, + "is_shared": false, + "name": null +} +``` -- GitLab From 1532d28c69ec77b7f1271a8ad2fa71937b47ab0d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 2 Feb 2016 13:17:41 +0200 Subject: [PATCH 06/36] Add note of deprecation in old Runners CI API --- doc/ci/api/runners.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md index c383dc4bcc94..d14d9b5269c4 100644 --- a/doc/ci/api/runners.md +++ b/doc/ci/api/runners.md @@ -1,5 +1,8 @@ # Runners API +_**Note:** In GitLab 8.0, GitLab CI was merged in GitLab itself. This document +is deprecated in favor of the [new Runners API](../../api/runners.md)._ + ## Runners ### Retrieve all runners @@ -74,4 +77,4 @@ Returns: "updated_at" : "2015-02-26T11:39:39.232Z", "description" : "awesome runner" } -``` \ No newline at end of file +``` -- GitLab From cd62c7470019a88cfda7c2d54a14eaf8db21e9d5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 2 Feb 2016 14:22:51 +0200 Subject: [PATCH 07/36] Add `Example response` above each json output [ci skip] --- doc/api/runners.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index e0e3d35535ce..23486de3726a 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -8,7 +8,7 @@ Get a list of runners. GET /runners ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| | `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | @@ -16,6 +16,8 @@ GET /runners curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" ``` +Example response: + ```json [ { @@ -43,7 +45,7 @@ Get details of a runner. GET /runners/:id ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| | `id` | integer | yes | The ID of a runner | @@ -51,6 +53,8 @@ GET /runners/:id curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" ``` +Example response: + ```json { "active": true, @@ -78,7 +82,7 @@ Update details of a runner. PUT /runners/:id ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |---------------|---------|----------|---------------------| | `id` | integer | yes | The ID of a runner | | `description` | string | no | The description of a runner | @@ -89,6 +93,8 @@ PUT /runners/:id curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2" ``` +Example response: + ```json { "active": true, @@ -118,7 +124,7 @@ Remove a runner. DELETE /runners/:id ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| | `id` | integer | yes | The ID of a runner | @@ -126,6 +132,8 @@ DELETE /runners/:id curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" ``` +Example response: + ```json { "active": true, @@ -151,7 +159,7 @@ is defined **and** shared runners usage is enabled in project's settings. GET /projects/:id/runners ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| | `id` | integer | yes | The ID of a project | @@ -159,6 +167,8 @@ GET /projects/:id/runners curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" ``` +Example response: + ```json [ { @@ -186,7 +196,7 @@ Enable available specific runner in project. PUT /projects/:id/runners/:runner_id ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-------------|---------|----------|---------------------| | `id` | integer | yes | The ID of a project | | `runner_id` | integer | yes | The ID of a runner | @@ -195,6 +205,8 @@ PUT /projects/:id/runners/:runner_id curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" ``` +Example response: + ```json { "active": true, @@ -214,7 +226,7 @@ specified runner. If so, then an error is returned and user should use the [remo PUT /projects/:id/runners/:runner_id ``` -| Attribute | Type | required | Description | +| Attribute | Type | Required | Description | |-------------|---------|----------|---------------------| | `id` | integer | yes | The ID of a project | | `runner_id` | integer | yes | The ID of a runner | @@ -223,6 +235,8 @@ PUT /projects/:id/runners/:runner_id curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" ``` +Example response: + ```json { "active": true, -- GitLab From b56ee397bb5fa32e3a53fdaee3d6592f1486d7f1 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 2 Feb 2016 14:52:33 +0100 Subject: [PATCH 08/36] Add some fixes in runners API documentation --- doc/api/runners.md | 8 ++++---- lib/api/runners.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 23486de3726a..3be3fd17e659 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -164,7 +164,7 @@ GET /projects/:id/runners | `id` | integer | yes | The ID of a project | ``` -curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" +curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" ``` Example response: @@ -193,7 +193,7 @@ Example response: Enable available specific runner in project. ``` -PUT /projects/:id/runners/:runner_id +POST /projects/:id/runners/:runner_id ``` | Attribute | Type | Required | Description | @@ -202,7 +202,7 @@ PUT /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" ``` Example response: @@ -223,7 +223,7 @@ Disable a specific runner from project. It works only, if the project isn't an o specified runner. If so, then an error is returned and user should use the [remove a runner](#remove-a-runner) feature. ``` -PUT /projects/:id/runners/:runner_id +DELETE /projects/:id/runners/:runner_id ``` | Attribute | Type | Required | Description | diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 799c10a18973..f4f8f2f2247c 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -87,8 +87,8 @@ class Runners < Grape::API # id (required) - The ID of the project # runner_id (required) - The ID of the runner # Example Request: - # PUT /projects/:id/runners/:runner_id - put ':id/runners/:runner_id' do + # POST /projects/:id/runners/:runner_id + post ':id/runners/:runner_id' do runner = get_runner(params[:runner_id]) can_enable_runner?(runner) Ci::RunnerProject.create(runner: runner, project: user_project) -- GitLab From 81ced6f55b1ab847bbb21da9c43410340c95c2b3 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 2 Feb 2016 15:52:02 +0100 Subject: [PATCH 09/36] Split `/runners` entrypoint to `/runners` and `/runners/all` --- doc/api/runners.md | 55 +++++++++++++++++++++++++++++-- lib/api/runners.rb | 21 +++++++----- spec/requests/api/runners_spec.rb | 54 +++++++++++++++++++++++------- 3 files changed, 108 insertions(+), 22 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 3be3fd17e659..a8b95ee49eda 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -1,8 +1,8 @@ # Runners API -## List runners +## List owned runners -Get a list of runners. +Get a list of specific runners available for user. ``` GET /runners @@ -37,6 +37,57 @@ Example response: ] ``` +## List all runners + +Get a list of all runners (specific and shared). Access restricted to users with `admin` privileges. + +``` +GET /runners/all +``` + +| Attribute | Type | Required | Description | +|-----------|---------|----------|---------------------| +| `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | + +``` +curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all" +``` + +Example response: + +```json +[ + { + "active": true, + "description": "shared-runner-1", + "id": 1, + "is_shared": true, + "name": null + }, + { + "active": true, + "description": "shared-runner-2", + "id": 3, + "is_shared": true, + "name": null + }, + { + "active": true, + "description": "test-1-20150125", + "id": 6, + "is_shared": false, + "name": null + }, + { + "active": true, + "description": "test-2-20150125", + "id": 8, + "is_shared": false, + "name": null + } +] +``` + ## Get runner's details Get details of a runner. diff --git a/lib/api/runners.rb b/lib/api/runners.rb index f4f8f2f2247c..284909c8db4b 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -4,19 +4,22 @@ class Runners < Grape::API before { authenticate! } resource :runners do - # Get available shared runners + # Get runners available for user # # Example Request: # GET /runners get do - runners = - if current_user.is_admin? - Ci::Runner.all - else - current_user.ci_authorized_runners - end - - runners = filter_runners(runners, params[:scope]) + runners = filter_runners(current_user.ci_authorized_runners, params[:scope]) + present paginate(runners), with: Entities::Runner + end + + # Get all runners - shared and specific + # + # Example Request: + # GET /runners/all + get 'all' do + authenticated_as_admin! + runners = filter_runners(Ci::Runner.all, params[:scope]) present paginate(runners), with: Entities::Runner end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index d600b2312c5c..a3c96777b925 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -23,9 +23,44 @@ describe 'GET /runners' do context 'authorized user' do - context 'authorized user with admin privileges' do + it 'should return user available runners' do + get api('/runners', user) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_falsey + end + + it 'should filter runners by scope' do + get api('/runners?scope=specific', user) + shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(shared).to be_falsey + end + + it 'should avoid filtering if scope is invalid' do + get api('/runners?scope=unknown', user) + expect(response.status).to eq(400) + end + end + + context 'unauthorized user' do + it 'should not return runners' do + get api('/runners') + + expect(response.status).to eq(401) + end + end + end + + describe 'GET /runners/all' do + context 'authorized user' do + context 'with admin privileges' do it 'should return all runners' do - get api('/runners', admin) + get api('/runners/all', admin) shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) @@ -34,19 +69,16 @@ end end - context 'authorized user without admin privileges' do - it 'should return user available runners' do - get api('/runners', user) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + context 'without admin privileges' do + it 'should not return runners list' do + get api('/runners/all', user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(shared).to be_falsey + expect(response.status).to eq(403) end end it 'should filter runners by scope' do - get api('/runners?scope=specific', user) + get api('/runners?scope=specific', admin) shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) @@ -55,7 +87,7 @@ end it 'should avoid filtering if scope is invalid' do - get api('/runners?scope=unknown', user) + get api('/runners?scope=unknown', admin) expect(response.status).to eq(400) end end -- GitLab From 16b3368af32084b19436012170dd461e12cf028a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 2 Feb 2016 16:31:52 +0100 Subject: [PATCH 10/36] Fix runners API spec --- spec/requests/api/runners_spec.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index a3c96777b925..27ce8c08eb34 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -316,24 +316,24 @@ end end - describe 'PUT /projects/:id/runners/:runner_id' do + describe 'POST /projects/:id/runners/:runner_id' do context 'authorized user' do it 'should enable specific runner' do expect do - put api("/projects/#{project.id}/runners/#{specific_runner2.id}", user) + post api("/projects/#{project.id}/runners/#{specific_runner2.id}", user) end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(200) + expect(response.status).to eq(201) end it 'should avoid changes when enabling already enabled runner' do expect do - put api("/projects/#{project.id}/runners/#{specific_runner.id}", user) + post api("/projects/#{project.id}/runners/#{specific_runner.id}", user) end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(200) + expect(response.status).to eq(201) end it 'should not enable shared runner' do - put api("/projects/#{project.id}/runners/#{shared_runner.id}", user) + post api("/projects/#{project.id}/runners/#{shared_runner.id}", user) expect(response.status).to eq(403) end @@ -341,15 +341,15 @@ context 'user is admin' do it 'should enable any specific runner' do expect do - put api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", admin) + post api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", admin) end.to change{ project.runners.count }.by(+1) - expect(response.status).to eq(200) + expect(response.status).to eq(201) end end context 'user is not admin' do it 'should not enable runner without access to' do - put api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", user) + post api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", user) expect(response.status).to eq(403) end @@ -358,7 +358,7 @@ context 'authorized user without permissions' do it 'should not enable runner' do - put api("/projects/#{project.id}/runners/#{specific_runner2.id}", user2) + post api("/projects/#{project.id}/runners/#{specific_runner2.id}", user2) expect(response.status).to eq(403) end @@ -366,7 +366,7 @@ context 'unauthorized user' do it 'should not enable runner' do - put api("/projects/#{project.id}/runners/#{specific_runner2.id}") + post api("/projects/#{project.id}/runners/#{specific_runner2.id}") expect(response.status).to eq(401) end -- GitLab From f562a477f227ec8acdbf491e08f365f1adf24647 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 2 Feb 2016 18:47:02 +0100 Subject: [PATCH 11/36] Add associated project info to runner details output --- doc/api/runners.md | 7 +++++++ lib/api/entities.rb | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/doc/api/runners.md b/doc/api/runners.md index a8b95ee49eda..ab73250fc7cc 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -116,6 +116,13 @@ Example response: "last_contact": "2016-01-25T16:39:48.066Z", "name": null, "platform": null, + "projects": [ + { + "id": 1, + "name": "GitLab.org / GitLab Community Edition", + "path": "gitlab-org/gitlab-ce" + } + ], "revision": null, "tag_list": [ "ruby", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bdbf2cf50821..0170fa5a6541 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -369,6 +369,12 @@ class TriggerRequest < Grape::Entity expose :id, :variables end + class RunnerProjectDetails < Grape::Entity + expose :id + expose :name_with_namespace, as: :name + expose :path_with_namespace, as: :path + end + class Runner < Grape::Entity expose :id expose :description @@ -381,6 +387,7 @@ class RunnerDetails < Runner expose :tag_list expose :version, :revision, :platform, :architecture expose :contacted_at, as: :last_contact + expose :projects, with: Entities::RunnerProjectDetails end class Build < Grape::Entity -- GitLab From d4f1bcdd284e476180c015462a44d076116035b0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Feb 2016 11:14:24 +0200 Subject: [PATCH 12/36] Note that `ci/api/runners.rb` is still used [ci skip] --- doc/ci/api/runners.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/ci/api/runners.md b/doc/ci/api/runners.md index d14d9b5269c4..e9033aeacd55 100644 --- a/doc/ci/api/runners.md +++ b/doc/ci/api/runners.md @@ -1,7 +1,8 @@ # Runners API -_**Note:** In GitLab 8.0, GitLab CI was merged in GitLab itself. This document -is deprecated in favor of the [new Runners API](../../api/runners.md)._ +_**Note:** This API is intended to be used only by Runners as their own +communication channel. For the consumer API see the +[new Runners API](../../api/runners.md)._ ## Runners -- GitLab From a09ae17337147e5a51f3cc6a342ca02747d48c33 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Feb 2016 15:25:33 +0200 Subject: [PATCH 13/36] Correct PRIVATE-TOKEN to use dash instead of underscore --- doc/api/runners.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index ab73250fc7cc..213b9a996783 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -13,7 +13,7 @@ GET /runners | `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | ``` -curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" ``` Example response: @@ -50,7 +50,7 @@ GET /runners/all | `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | ``` -curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all" ``` Example response: @@ -101,7 +101,7 @@ GET /runners/:id | `id` | integer | yes | The ID of a runner | ``` -curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" ``` Example response: @@ -148,7 +148,7 @@ PUT /runners/:id | `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner | ``` -curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2" +curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2" ``` Example response: @@ -187,7 +187,7 @@ DELETE /runners/:id | `id` | integer | yes | The ID of a runner | ``` -curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" ``` Example response: @@ -222,7 +222,7 @@ GET /projects/:id/runners | `id` | integer | yes | The ID of a project | ``` -curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" ``` Example response: @@ -260,7 +260,7 @@ POST /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" ``` Example response: @@ -290,7 +290,7 @@ DELETE /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" ``` Example response: -- GitLab From dc32af950821946f5bc3a4e57b4b7eeb0ffb032f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Feb 2016 15:39:55 +0200 Subject: [PATCH 14/36] More fixes in runners doc [ci skip] --- doc/api/runners.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 213b9a996783..8e7351d01ec4 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -2,10 +2,11 @@ ## List owned runners -Get a list of specific runners available for user. +Get a list of runners available to the user. ``` GET /runners +GET /runners?scope=active ``` | Attribute | Type | Required | Description | @@ -39,10 +40,12 @@ Example response: ## List all runners -Get a list of all runners (specific and shared). Access restricted to users with `admin` privileges. +Get a list of all runners in the GitLab instance (specific and shared). Access +is restricted to users with `admin` privileges. ``` GET /runners/all +GET /runners?scope=online ``` | Attribute | Type | Required | Description | @@ -210,8 +213,9 @@ Example response: ## List project's runners -List all runners (*shared* and *specific*) available in project. Shared runners are listed if at least one shared runner -is defined **and** shared runners usage is enabled in project's settings. +List all runners (specific and shared) available in the project. Shared runners +are listed if at least one shared runner is defined **and** shared runners +usage is enabled in the project's settings. ``` GET /projects/:id/runners @@ -222,7 +226,7 @@ GET /projects/:id/runners | `id` | integer | yes | The ID of a project | ``` -curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" ``` Example response: @@ -248,7 +252,7 @@ Example response: ## Enable a runner in project -Enable available specific runner in project. +Enable an available specific runner in the project. ``` POST /projects/:id/runners/:runner_id @@ -277,8 +281,9 @@ Example response: ## Disable a runner from project -Disable a specific runner from project. It works only, if the project isn't an only project associated with the -specified runner. If so, then an error is returned and user should use the [remove a runner](#remove-a-runner) feature. +Disable a specific runner from the project. It works only if the project isn't +the only project associated with the specified runner. If so, an error is +returned. Use the [Remove a runner](#remove-a-runner) call instead. ``` DELETE /projects/:id/runners/:runner_id -- GitLab From 553bac57d01f103f3f419e8096f30f422781adce Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 4 Feb 2016 10:33:29 +0100 Subject: [PATCH 15/36] Add token to runner details output in API --- doc/api/runners.md | 1 + lib/api/entities.rb | 1 + lib/api/runners.rb | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 8e7351d01ec4..926c46eb8ed6 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -126,6 +126,7 @@ Example response: "path": "gitlab-org/gitlab-ce" } ], + "token": "205086a8e3b9a2b818ffac9b89d102", "revision": null, "tag_list": [ "ruby", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0170fa5a6541..af030159580c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -387,6 +387,7 @@ class RunnerDetails < Runner expose :tag_list expose :version, :revision, :platform, :architecture expose :contacted_at, as: :last_contact + expose :token, if: lambda { |runner, options| options[:user_is_admin] || !runner.is_shared? } expose :projects, with: Entities::RunnerProjectDetails end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 284909c8db4b..157824743691 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -33,7 +33,7 @@ class Runners < Grape::API runner = get_runner(params[:id]) can_show_runner?(runner) unless current_user.is_admin? - present runner, with: Entities::RunnerDetails + present runner, with: Entities::RunnerDetails, user_is_admin: current_user.is_admin? end # Update runner's details -- GitLab From b58744cd93ed572e4a40ab3f35ff69753a4cd42b Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 4 Feb 2016 11:01:16 +0100 Subject: [PATCH 16/36] Modify authentication check methods in runners API --- lib/api/runners.rb | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 157824743691..c08d6729dd82 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -31,7 +31,7 @@ class Runners < Grape::API # GET /runners/:id get ':id' do runner = get_runner(params[:id]) - can_show_runner?(runner) unless current_user.is_admin? + authenticate_show_runner!(runner) present runner, with: Entities::RunnerDetails, user_is_admin: current_user.is_admin? end @@ -47,7 +47,7 @@ class Runners < Grape::API # PUT /runners/:id put ':id' do runner = get_runner(params[:id]) - can_update_runner?(runner) unless current_user.is_admin? + authenticate_update_runner!(runner) attrs = attributes_for_keys [:description, :active, :tag_list] if runner.update(attrs) @@ -65,7 +65,7 @@ class Runners < Grape::API # DELETE /runners/:id delete ':id' do runner = get_runner(params[:id]) - can_delete_runner?(runner) + authenticate_delete_runner!(runner) runner.destroy! present runner, with: Entities::RunnerDetails @@ -93,7 +93,7 @@ class Runners < Grape::API # POST /projects/:id/runners/:runner_id post ':id/runners/:runner_id' do runner = get_runner(params[:runner_id]) - can_enable_runner?(runner) + authenticate_enable_runner!(runner) Ci::RunnerProject.create(runner: runner, project: user_project) present runner, with: Entities::Runner @@ -111,7 +111,7 @@ class Runners < Grape::API not_found!('Runner') unless runner_project runner = runner_project.runner - forbidden!("Can't disable runner - only one project associated with it. Please remove runner instead") if runner.projects.count == 1 + forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 runner_project.destroy @@ -137,34 +137,32 @@ def get_runner(id) runner end - def can_show_runner?(runner) - return true if runner.is_shared - forbidden!("Can't show runner's details - no access granted") unless user_can_access_runner?(runner) + def authenticate_show_runner!(runner) + return if runner.is_shared || current_user.is_admin? + forbidden!("No access granted") unless user_can_access_runner?(runner) end - def can_update_runner?(runner) - return true if current_user.is_admin? - forbidden!("Can't update shared runner") if runner.is_shared? - forbidden!("Can't update runner - no access granted") unless user_can_access_runner?(runner) + def authenticate_update_runner!(runner) + return if current_user.is_admin? + forbidden!("Runner is shared") if runner.is_shared? + forbidden!("No access granted") unless user_can_access_runner?(runner) end - def can_delete_runner?(runner) - return true if current_user.is_admin? - forbidden!("Can't delete shared runner") if runner.is_shared? - forbidden!("Can't delete runner - associated with more than one project") if runner.projects.count > 1 - forbidden!("Can't delete runner - no access granted") unless user_can_access_runner?(runner) + def authenticate_delete_runner!(runner) + return if current_user.is_admin? + forbidden!("Runner is shared") if runner.is_shared? + forbidden!("Runner associated with more than one project") if runner.projects.count > 1 + forbidden!("No access granted") unless user_can_access_runner?(runner) end - def can_enable_runner?(runner) - forbidden!("Can't enable shared runner directly") if runner.is_shared? - return true if current_user.is_admin? - forbidden!("Can't update runner - no access granted") unless user_can_access_runner?(runner) + def authenticate_enable_runner!(runner) + forbidden!("Runner is shared") if runner.is_shared? + return if current_user.is_admin? + forbidden!("No access granted") unless user_can_access_runner?(runner) end def user_can_access_runner?(runner) - runner.projects.inject(false) do |final, project| - final || abilities.allowed?(current_user, :admin_project, project) - end + current_user.ci_authorized_runners.exists?(runner.id) end end end -- GitLab From f21b15d5f5af150ef39f338a4d4afb495402311a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 4 Feb 2016 14:53:53 +0100 Subject: [PATCH 17/36] Limit projects to user available projects if user is not an admin --- lib/api/entities.rb | 8 +++++++- lib/api/runners.rb | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index af030159580c..a8c00542d357 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -388,7 +388,13 @@ class RunnerDetails < Runner expose :version, :revision, :platform, :architecture expose :contacted_at, as: :last_contact expose :token, if: lambda { |runner, options| options[:user_is_admin] || !runner.is_shared? } - expose :projects, with: Entities::RunnerProjectDetails + expose :projects, with: Entities::RunnerProjectDetails do |runner, options| + if options[:user_is_admin] + runner.projects + else + runner.projects.where(id: options[:available_projects_ids]) + end + end end class Build < Grape::Entity diff --git a/lib/api/runners.rb b/lib/api/runners.rb index c08d6729dd82..4a0e68a4ddbd 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -33,7 +33,11 @@ class Runners < Grape::API runner = get_runner(params[:id]) authenticate_show_runner!(runner) - present runner, with: Entities::RunnerDetails, user_is_admin: current_user.is_admin? + available_projects_ids = runner.projects.select{ |p| can?(current_user, :read_project, p) } + .map(&:id) unless current_user.is_admin? + + present runner, with: Entities::RunnerDetails, user_is_admin: current_user.is_admin?, + available_projects_ids: available_projects_ids end # Update runner's details -- GitLab From 97c88966bb9594449a519d377203206dd6416868 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 5 Feb 2016 13:25:16 +0200 Subject: [PATCH 18/36] GET /runners is for specific runners only [ci skip] --- doc/api/runners.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 926c46eb8ed6..aa58c4431b8a 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -2,7 +2,7 @@ ## List owned runners -Get a list of runners available to the user. +Get a list of specific runners available to the user. ``` GET /runners @@ -11,7 +11,7 @@ GET /runners?scope=active | Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| -| `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided | +| `scope` | string | no | The scope of specific runners to show, one of: `active`, `paused`, `online`; showing all runners if none provided | ``` curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners" @@ -45,7 +45,7 @@ is restricted to users with `admin` privileges. ``` GET /runners/all -GET /runners?scope=online +GET /runners/all?scope=online ``` | Attribute | Type | Required | Description | -- GitLab From 36e7ffea5de84102a7306faf679cdb5b81920f19 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 5 Feb 2016 15:35:21 +0100 Subject: [PATCH 19/36] Fix runners filtering --- lib/api/runners.rb | 8 ++++++-- spec/requests/api/runners_spec.rb | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 4a0e68a4ddbd..e807d2eccf0d 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -9,7 +9,7 @@ class Runners < Grape::API # Example Request: # GET /runners get do - runners = filter_runners(current_user.ci_authorized_runners, params[:scope]) + runners = filter_runners(current_user.ci_authorized_runners, params[:scope], without: ['specific', 'shared']) present paginate(runners), with: Entities::Runner end @@ -124,10 +124,14 @@ class Runners < Grape::API end helpers do - def filter_runners(runners, scope) + def filter_runners(runners, scope, options = {}) return runners unless scope.present? available_scopes = ::Ci::Runner::AVAILABLE_SCOPES + if options[:without] + available_scopes = available_scopes - options[:without] + end + if (available_scopes & [scope]).empty? render_api_error!('Scope contains invalid value', 400) end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 27ce8c08eb34..efb81bed03f5 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -33,7 +33,7 @@ end it 'should filter runners by scope' do - get api('/runners?scope=specific', user) + get api('/runners?scope=active', user) shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) @@ -78,7 +78,7 @@ end it 'should filter runners by scope' do - get api('/runners?scope=specific', admin) + get api('/runners/all?scope=specific', admin) shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) -- GitLab From 24eed1c5c1cbbad7081625ad98d06d151933f583 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 10 Feb 2016 12:26:56 +0100 Subject: [PATCH 20/36] Modify runner projects selecting method in runners API --- lib/api/entities.rb | 6 +++--- lib/api/runners.rb | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a8c00542d357..3f1594994c93 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -387,12 +387,12 @@ class RunnerDetails < Runner expose :tag_list expose :version, :revision, :platform, :architecture expose :contacted_at, as: :last_contact - expose :token, if: lambda { |runner, options| options[:user_is_admin] || !runner.is_shared? } + expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } expose :projects, with: Entities::RunnerProjectDetails do |runner, options| - if options[:user_is_admin] + if options[:current_user].is_admin? runner.projects else - runner.projects.where(id: options[:available_projects_ids]) + options[:current_user].authorized_projects.where(id: runner.projects) end end end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index e807d2eccf0d..03803ede9fcb 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -33,11 +33,7 @@ class Runners < Grape::API runner = get_runner(params[:id]) authenticate_show_runner!(runner) - available_projects_ids = runner.projects.select{ |p| can?(current_user, :read_project, p) } - .map(&:id) unless current_user.is_admin? - - present runner, with: Entities::RunnerDetails, user_is_admin: current_user.is_admin?, - available_projects_ids: available_projects_ids + present runner, with: Entities::RunnerDetails, current_user: current_user end # Update runner's details -- GitLab From d1ac00aea370cc81a575b2ea7a8ef655343cafd3 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 10 Feb 2016 14:20:51 +0100 Subject: [PATCH 21/36] Modify and fix output of delete and update of a runner --- doc/api/runners.md | 14 ++++++++------ lib/api/runners.rb | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index aa58c4431b8a..876dd3042085 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -167,6 +167,14 @@ Example response: "last_contact": "2016-01-25T16:39:48.066Z", "name": null, "platform": null, + "projects": [ + { + "id": 1, + "name": "GitLab.org / GitLab Community Edition", + "path": "gitlab-org/gitlab-ce" + } + ], + "token": "205086a8e3b9a2b818ffac9b89d102", "revision": null, "tag_list": [ "ruby", @@ -199,16 +207,10 @@ Example response: ```json { "active": true, - "architecture": null, "description": "test-1-20150125-test", "id": 6, "is_shared": false, - "last_contact": "2016-01-25T16:39:48.066Z", "name": null, - "platform": null, - "revision": null, - "tag_list": [], - "version": null } ``` diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 03803ede9fcb..0c1587451249 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -51,7 +51,7 @@ class Runners < Grape::API attrs = attributes_for_keys [:description, :active, :tag_list] if runner.update(attrs) - present runner, with: Entities::RunnerDetails + present runner, with: Entities::RunnerDetails, current_user: current_user else render_validation_error!(runner) end @@ -68,7 +68,7 @@ class Runners < Grape::API authenticate_delete_runner!(runner) runner.destroy! - present runner, with: Entities::RunnerDetails + present runner, with: Entities::Runner end end -- GitLab From dc182dc50e61bc4d4cde3fb32ee29668ad24513b Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 15 Feb 2016 12:55:41 +0100 Subject: [PATCH 22/36] Add some modifications to spec/requests/api/runners_spec.rb --- spec/requests/api/runners_spec.rb | 200 +++++++++++++++++------------- 1 file changed, 112 insertions(+), 88 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index efb81bed03f5..b95bfb15040d 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -14,8 +14,6 @@ let!(:shared_runner) { create(:ci_shared_runner, tag_list: ['mysql', 'ruby'], active: true) } let!(:specific_runner) { create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) } let!(:specific_runner_project) { create(:ci_runner_project, runner: specific_runner, project: project) } - let!(:specific_runner2) { create(:ci_specific_runner) } - let!(:specific_runner2_project) { create(:ci_runner_project, runner: specific_runner2, project: project2) } let!(:unused_specific_runner) { create(:ci_specific_runner) } let!(:two_projects_runner) { create(:ci_specific_runner) } let!(:two_projects_runner_project) { create(:ci_runner_project, runner: two_projects_runner, project: project) } @@ -25,7 +23,7 @@ context 'authorized user' do it 'should return user available runners' do get api('/runners', user) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -34,7 +32,7 @@ it 'should filter runners by scope' do get api('/runners?scope=active', user) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -61,7 +59,7 @@ context 'with admin privileges' do it 'should return all runners' do get api('/runners/all', admin) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -79,7 +77,7 @@ it 'should filter runners by scope' do get api('/runners/all?scope=specific', admin) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -103,18 +101,22 @@ describe 'GET /runners/:id' do context 'admin user' do - it "should return runner's details" do - get api("/runners/#{specific_runner.id}", admin) + context 'when runner is shared' do + it "should return runner's details" do + get api("/runners/#{shared_runner.id}", admin) - expect(response.status).to eq(200) - expect(json_response['description']).to eq(specific_runner.description) + expect(response.status).to eq(200) + expect(json_response['description']).to eq(shared_runner.description) + end end - it "should return shared runner's details" do - get api("/runners/#{shared_runner.id}", admin) + context 'when runner is not shared' do + it "should return runner's details" do + get api("/runners/#{specific_runner.id}", admin) - expect(response.status).to eq(200) - expect(json_response['description']).to eq(shared_runner.description) + expect(response.status).to eq(200) + expect(json_response['description']).to eq(specific_runner.description) + end end it 'should return 404 if runner does not exists' do @@ -125,18 +127,22 @@ end context "runner project's administrative user" do - it "should return runner's details" do - get api("/runners/#{specific_runner.id}", user) + context 'when runner is not shared' do + it "should return runner's details" do + get api("/runners/#{specific_runner.id}", user) - expect(response.status).to eq(200) - expect(json_response['description']).to eq(specific_runner.description) + expect(response.status).to eq(200) + expect(json_response['description']).to eq(specific_runner.description) + end end - it "should return shared runner's details" do - get api("/runners/#{shared_runner.id}", user) + context 'when runner is shared' do + it "should return runner's details" do + get api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(200) - expect(json_response['description']).to eq(shared_runner.description) + expect(response.status).to eq(200) + expect(json_response['description']).to eq(shared_runner.description) + end end end @@ -159,28 +165,32 @@ describe 'PUT /runners/:id' do context 'admin user' do - it 'should update shared runner' do - description = shared_runner.description - active = shared_runner.active - tag_list = shared_runner.tag_list - put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active, - tag_list: ['ruby2.1', 'pgsql', 'mysql'] - shared_runner.reload + context 'when runner is shared' do + it 'should update runner' do + description = shared_runner.description + active = shared_runner.active + tag_list = shared_runner.tag_list + put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active, + tag_list: ['ruby2.1', 'pgsql', 'mysql'] + shared_runner.reload - expect(response.status).to eq(200) - expect(shared_runner.description).not_to eq(description) - expect(shared_runner.active).not_to eq(active) - expect(shared_runner.tag_list).not_to eq(tag_list) + expect(response.status).to eq(200) + expect(shared_runner.description).not_to eq(description) + expect(shared_runner.active).not_to eq(active) + expect(shared_runner.tag_list).not_to eq(tag_list) + end end - it 'should update specific runner' do - description = specific_runner.description - put api("/runners/#{specific_runner.id}", admin), description: 'test' - specific_runner.reload + context 'when runner is not shared' do + it 'should update runner' do + description = specific_runner.description + put api("/runners/#{specific_runner.id}", admin), description: 'test' + specific_runner.reload - expect(response.status).to eq(200) - expect(specific_runner.description).to eq('test') - expect(specific_runner.description).not_to eq(description) + expect(response.status).to eq(200) + expect(specific_runner.description).to eq('test') + expect(specific_runner.description).not_to eq(description) + end end it 'should return 404 if runner does not exists' do @@ -191,28 +201,31 @@ end context 'authorized user' do - it 'should not update shared runner' do - put api("/runners/#{shared_runner.id}", user), description: 'test' + context 'when runner is shared' do + it 'should not update runner' do + put api("/runners/#{shared_runner.id}", user), description: 'test' - expect(response.status).to eq(403) + expect(response.status).to eq(403) + end end - it 'should not update specific runner without access to' do - put api("/runners/#{specific_runner.id}", user2), description: 'test' + context 'when runner is not shared' do + it 'should not update runner without access to it' do + put api("/runners/#{specific_runner.id}", user2), description: 'test' - expect(response.status).to eq(403) - end + expect(response.status).to eq(403) + end - it 'should update specific runner' do - description = specific_runner.description - put api("/runners/#{specific_runner.id}", admin), description: 'test' - specific_runner.reload + it 'should update runner with access to it' do + description = specific_runner.description + put api("/runners/#{specific_runner.id}", admin), description: 'test' + specific_runner.reload - expect(response.status).to eq(200) - expect(specific_runner.description).to eq('test') - expect(specific_runner.description).not_to eq(description) + expect(response.status).to eq(200) + expect(specific_runner.description).to eq('test') + expect(specific_runner.description).not_to eq(description) + end end - end context 'unauthorized user' do @@ -226,25 +239,29 @@ describe 'DELETE /runners/:id' do context 'admin user' do - it 'should delete shared runner' do - expect do - delete api("/runners/#{shared_runner.id}", admin) - end.to change{ Ci::Runner.shared.count }.by(-1) - expect(response.status).to eq(200) + context 'when runner is shared' do + it 'should delete runner' do + expect do + delete api("/runners/#{shared_runner.id}", admin) + end.to change{ Ci::Runner.shared.count }.by(-1) + expect(response.status).to eq(200) + end end - it 'should delete unused specific runner' do - expect do - delete api("/runners/#{unused_specific_runner.id}", admin) - end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) - end + context 'when runner is not shared' do + it 'should delete unused runner' do + expect do + delete api("/runners/#{unused_specific_runner.id}", admin) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end - it 'should delete used specific runner' do - expect do - delete api("/runners/#{specific_runner.id}", admin) - end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + it 'should delete used runner' do + expect do + delete api("/runners/#{specific_runner.id}", admin) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end end it 'should return 404 if runner does not exists' do @@ -255,26 +272,30 @@ end context 'authorized user' do - it 'should not delete shared runner' do - delete api("/runners/#{shared_runner.id}", user) - expect(response.status).to eq(403) + context 'when runner is shared' do + it 'should not delete runner' do + delete api("/runners/#{shared_runner.id}", user) + expect(response.status).to eq(403) + end end - it 'should not delete runner without access to' do - delete api("/runners/#{specific_runner.id}", user2) - expect(response.status).to eq(403) - end + context 'when runner is not shared' do + it 'should not delete runner without access to it' do + delete api("/runners/#{specific_runner.id}", user2) + expect(response.status).to eq(403) + end - it 'should not delete runner with more than one associated project' do - delete api("/runners/#{two_projects_runner.id}", user) - expect(response.status).to eq(403) - end + it 'should not delete runner with more than one associated project' do + delete api("/runners/#{two_projects_runner.id}", user) + expect(response.status).to eq(403) + end - it 'should delete runner for one owned project' do - expect do - delete api("/runners/#{specific_runner.id}", user) - end.to change{ Ci::Runner.specific.count }.by(-1) - expect(response.status).to eq(200) + it 'should delete runner for one owned project' do + expect do + delete api("/runners/#{specific_runner.id}", user) + end.to change{ Ci::Runner.specific.count }.by(-1) + expect(response.status).to eq(200) + end end end @@ -291,7 +312,7 @@ context 'authorized user with master privileges' do it "should return project's runners" do get api("/projects/#{project.id}/runners", user) - shared = false || json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -317,6 +338,9 @@ end describe 'POST /projects/:id/runners/:runner_id' do + let!(:specific_runner2) { create(:ci_specific_runner) } + let!(:specific_runner2_project) { create(:ci_runner_project, runner: specific_runner2, project: project2) } + context 'authorized user' do it 'should enable specific runner' do expect do -- GitLab From b36116f9ad3990cb0d5c81ecb6d5b306dc41fbd5 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 16 Feb 2016 12:05:48 +0100 Subject: [PATCH 23/36] Move :runner_id param to POST body when enabling specific runner in project --- lib/api/runners.rb | 4 +++- spec/requests/api/runners_spec.rb | 22 ++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 0c1587451249..8ec91485b26b 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -91,7 +91,9 @@ class Runners < Grape::API # runner_id (required) - The ID of the runner # Example Request: # POST /projects/:id/runners/:runner_id - post ':id/runners/:runner_id' do + post ':id/runners' do + required_attributes! [:runner_id] + runner = get_runner(params[:runner_id]) authenticate_enable_runner!(runner) Ci::RunnerProject.create(runner: runner, project: user_project) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index b95bfb15040d..6261d0e840c6 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -337,27 +337,27 @@ end end - describe 'POST /projects/:id/runners/:runner_id' do + describe 'POST /projects/:id/runners' do let!(:specific_runner2) { create(:ci_specific_runner) } let!(:specific_runner2_project) { create(:ci_runner_project, runner: specific_runner2, project: project2) } context 'authorized user' do it 'should enable specific runner' do expect do - post api("/projects/#{project.id}/runners/#{specific_runner2.id}", user) + post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(+1) expect(response.status).to eq(201) end it 'should avoid changes when enabling already enabled runner' do expect do - post api("/projects/#{project.id}/runners/#{specific_runner.id}", user) + post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id end.to change{ project.runners.count }.by(0) expect(response.status).to eq(201) end it 'should not enable shared runner' do - post api("/projects/#{project.id}/runners/#{shared_runner.id}", user) + post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id expect(response.status).to eq(403) end @@ -365,7 +365,7 @@ context 'user is admin' do it 'should enable any specific runner' do expect do - post api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", admin) + post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id end.to change{ project.runners.count }.by(+1) expect(response.status).to eq(201) end @@ -373,16 +373,22 @@ context 'user is not admin' do it 'should not enable runner without access to' do - post api("/projects/#{project.id}/runners/#{unused_specific_runner.id}", user) + post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id expect(response.status).to eq(403) end end + + it 'should raise an error when no runner_id param is provided' do + post api("/projects/#{project.id}/runners", admin) + + expect(response.status).to eq(400) + end end context 'authorized user without permissions' do it 'should not enable runner' do - post api("/projects/#{project.id}/runners/#{specific_runner2.id}", user2) + post api("/projects/#{project.id}/runners", user2), runner_id: specific_runner2.id expect(response.status).to eq(403) end @@ -390,7 +396,7 @@ context 'unauthorized user' do it 'should not enable runner' do - post api("/projects/#{project.id}/runners/#{specific_runner2.id}") + post api("/projects/#{project.id}/runners"), runner_id: specific_runner2.id expect(response.status).to eq(401) end -- GitLab From 05e73356647cea999a707fd80625fcb92a93ce49 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 16 Feb 2016 12:43:43 +0100 Subject: [PATCH 24/36] Update docummentation - specific runner enabling [ci skip] --- doc/api/runners.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 876dd3042085..2cdd40590c1c 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -258,7 +258,7 @@ Example response: Enable an available specific runner in the project. ``` -POST /projects/:id/runners/:runner_id +POST /projects/:id/runners ``` | Attribute | Type | Required | Description | @@ -267,7 +267,7 @@ POST /projects/:id/runners/:runner_id | `runner_id` | integer | yes | The ID of a runner | ``` -curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners/9" +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/project/9/runners" -F "runner_id=9" ``` Example response: -- GitLab From 4ebadb77dd3a6f30728d039dad5ee240d0f44a63 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 16 Feb 2016 12:45:43 +0100 Subject: [PATCH 25/36] iChange `name` and `path` to `name_with_namespace` and `path_with_namespace` in `RunnerProjectDetails` --- doc/api/runners.md | 8 ++++---- lib/api/entities.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 2cdd40590c1c..600f238a406c 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -122,8 +122,8 @@ Example response: "projects": [ { "id": 1, - "name": "GitLab.org / GitLab Community Edition", - "path": "gitlab-org/gitlab-ce" + "name_with_namespace": "GitLab.org / GitLab Community Edition", + "path_with_namespace": "gitlab-org/gitlab-ce" } ], "token": "205086a8e3b9a2b818ffac9b89d102", @@ -170,8 +170,8 @@ Example response: "projects": [ { "id": 1, - "name": "GitLab.org / GitLab Community Edition", - "path": "gitlab-org/gitlab-ce" + "name_with_namespace": "GitLab.org / GitLab Community Edition", + "path_with_namespace": "gitlab-org/gitlab-ce" } ], "token": "205086a8e3b9a2b818ffac9b89d102", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3f1594994c93..09dae1e88e24 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -371,8 +371,8 @@ class TriggerRequest < Grape::Entity class RunnerProjectDetails < Grape::Entity expose :id - expose :name_with_namespace, as: :name - expose :path_with_namespace, as: :path + expose :name_with_namespace + expose :path_with_namespace end class Runner < Grape::Entity -- GitLab From 2ef196deb2c110d0c60d55f467f3116912841bea Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 13:03:39 +0100 Subject: [PATCH 26/36] Change interpolation to named placeholder in owned_or_shared scope --- app/models/ci/runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 1e914b444998..e725a6d468c2 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -41,7 +41,7 @@ class Runner < ActiveRecord::Base scope :owned_or_shared, ->(project_id) do joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id') - .where("ci_runner_projects.gl_project_id = #{project_id} OR ci_runners.is_shared = true") + .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end acts_as_taggable -- GitLab From 957c4de9b1e9d2de79f8f35f63e0143096ccd040 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 14:53:48 +0100 Subject: [PATCH 27/36] Reorganize `let` statements in spec/requests/api/runners_spec.rb --- spec/requests/api/runners_spec.rb | 41 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 6261d0e840c6..eb4327d885f2 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -6,18 +6,32 @@ let(:admin) { create(:user, :admin) } let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } - let!(:project2) { create(:project, creator_id: user.id) } - let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } - let!(:master2) { create(:project_member, user: user, project: project2, access_level: ProjectMember::MASTER) } - let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) } + + let(:project) { create(:project, creator_id: user.id) } + let(:project2) { create(:project, creator_id: user.id) } + let!(:shared_runner) { create(:ci_shared_runner, tag_list: ['mysql', 'ruby'], active: true) } - let!(:specific_runner) { create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) } - let!(:specific_runner_project) { create(:ci_runner_project, runner: specific_runner, project: project) } let!(:unused_specific_runner) { create(:ci_specific_runner) } - let!(:two_projects_runner) { create(:ci_specific_runner) } - let!(:two_projects_runner_project) { create(:ci_runner_project, runner: two_projects_runner, project: project) } - let!(:two_projects_runner_project2) { create(:ci_runner_project, runner: two_projects_runner, project: project2) } + + let!(:specific_runner) do + runner = create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) + create(:ci_runner_project, runner: runner, project: project) + runner + end + + let!(:two_projects_runner) do + runner = create(:ci_specific_runner) + create(:ci_runner_project, runner: runner, project: project) + create(:ci_runner_project, runner: runner, project: project2) + runner + end + + before do + # Set project access for users + create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) + create(:project_member, user: user, project: project2, access_level: ProjectMember::MASTER) + create(:project_member, user: user2, project: project, access_level: ProjectMember::REPORTER) + end describe 'GET /runners' do context 'authorized user' do @@ -338,8 +352,11 @@ end describe 'POST /projects/:id/runners' do - let!(:specific_runner2) { create(:ci_specific_runner) } - let!(:specific_runner2_project) { create(:ci_runner_project, runner: specific_runner2, project: project2) } + let(:specific_runner2) do + runner = create(:ci_specific_runner) + create(:ci_runner_project, runner: runner, project: project2) + runner + end context 'authorized user' do it 'should enable specific runner' do -- GitLab From d38322b8952292951fa008cb3300c73933f64fb4 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 15:27:28 +0100 Subject: [PATCH 28/36] Change `.map{...}.inject{...}` to `any?{...}` for searching shared runners --- spec/requests/api/runners_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index eb4327d885f2..6ad62f7d6fc7 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -37,7 +37,7 @@ context 'authorized user' do it 'should return user available runners' do get api('/runners', user) - shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.any?{ |r| r['is_shared'] } expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -46,7 +46,7 @@ it 'should filter runners by scope' do get api('/runners?scope=active', user) - shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.any?{ |r| r['is_shared'] } expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -73,7 +73,7 @@ context 'with admin privileges' do it 'should return all runners' do get api('/runners/all', admin) - shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.any?{ |r| r['is_shared'] } expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -91,7 +91,7 @@ it 'should filter runners by scope' do get api('/runners/all?scope=specific', admin) - shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.any?{ |r| r['is_shared'] } expect(response.status).to eq(200) expect(json_response).to be_an Array @@ -326,7 +326,7 @@ context 'authorized user with master privileges' do it "should return project's runners" do get api("/projects/#{project.id}/runners", user) - shared = json_response.map{ |r| r['is_shared'] }.inject{ |sum, shr| sum || shr} + shared = json_response.any?{ |r| r['is_shared'] } expect(response.status).to eq(200) expect(json_response).to be_an Array -- GitLab From a5540b385d91f0c16eaab1137d263c12fe1f2839 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 15:36:14 +0100 Subject: [PATCH 29/36] Modify expectations for update runner feature --- spec/requests/api/runners_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 6ad62f7d6fc7..d63786d4100d 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -183,15 +183,15 @@ it 'should update runner' do description = shared_runner.description active = shared_runner.active - tag_list = shared_runner.tag_list + put api("/runners/#{shared_runner.id}", admin), description: "#{description}_updated", active: !active, tag_list: ['ruby2.1', 'pgsql', 'mysql'] shared_runner.reload expect(response.status).to eq(200) - expect(shared_runner.description).not_to eq(description) - expect(shared_runner.active).not_to eq(active) - expect(shared_runner.tag_list).not_to eq(tag_list) + expect(shared_runner.description).to eq("#{description}_updated") + expect(shared_runner.active).to eq(!active) + expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') end end -- GitLab From f8f492e589e27078fe1d061b8ae2264bd798006c Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 15:48:58 +0100 Subject: [PATCH 30/36] Remove unnecessary parameters --- spec/requests/api/runners_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index d63786d4100d..1a4ec44a51b0 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -217,7 +217,7 @@ context 'authorized user' do context 'when runner is shared' do it 'should not update runner' do - put api("/runners/#{shared_runner.id}", user), description: 'test' + put api("/runners/#{shared_runner.id}", user) expect(response.status).to eq(403) end @@ -225,7 +225,7 @@ context 'when runner is not shared' do it 'should not update runner without access to it' do - put api("/runners/#{specific_runner.id}", user2), description: 'test' + put api("/runners/#{specific_runner.id}", user2) expect(response.status).to eq(403) end @@ -244,7 +244,7 @@ context 'unauthorized user' do it 'should not delete runner' do - put api("/runners/#{specific_runner.id}"), description: 'test' + put api("/runners/#{specific_runner.id}") expect(response.status).to eq(401) end @@ -405,7 +405,7 @@ context 'authorized user without permissions' do it 'should not enable runner' do - post api("/projects/#{project.id}/runners", user2), runner_id: specific_runner2.id + post api("/projects/#{project.id}/runners", user2) expect(response.status).to eq(403) end @@ -413,7 +413,7 @@ context 'unauthorized user' do it 'should not enable runner' do - post api("/projects/#{project.id}/runners"), runner_id: specific_runner2.id + post api("/projects/#{project.id}/runners") expect(response.status).to eq(401) end -- GitLab From 7ea60c8564b18788990ce8b45decd0932d71ba6e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 21:06:21 +0100 Subject: [PATCH 31/36] Replace Entities::RunnerProjectDetails with Entities::ForkedFromProject --- doc/api/runners.md | 4 ++++ lib/api/entities.rb | 8 +------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 600f238a406c..276c058e6424 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -122,7 +122,9 @@ Example response: "projects": [ { "id": 1, + "name": "GitLab Community Edition", "name_with_namespace": "GitLab.org / GitLab Community Edition", + "path": "gitlab-ce", "path_with_namespace": "gitlab-org/gitlab-ce" } ], @@ -170,7 +172,9 @@ Example response: "projects": [ { "id": 1, + "name": "GitLab Community Edition", "name_with_namespace": "GitLab.org / GitLab Community Edition", + "path": "gitlab-ce", "path_with_namespace": "gitlab-org/gitlab-ce" } ], diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 09dae1e88e24..3908e2a83c8c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -369,12 +369,6 @@ class TriggerRequest < Grape::Entity expose :id, :variables end - class RunnerProjectDetails < Grape::Entity - expose :id - expose :name_with_namespace - expose :path_with_namespace - end - class Runner < Grape::Entity expose :id expose :description @@ -388,7 +382,7 @@ class RunnerDetails < Runner expose :version, :revision, :platform, :architecture expose :contacted_at, as: :last_contact expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } - expose :projects, with: Entities::RunnerProjectDetails do |runner, options| + expose :projects, with: Entities::ForkedFromProject do |runner, options| if options[:current_user].is_admin? runner.projects else -- GitLab From e4d2f9972cef6268166a31c20c062b2bf42d9ed7 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 21:23:14 +0100 Subject: [PATCH 32/36] Change `last_contact` to `contacted_at` --- doc/api/runners.md | 4 ++-- lib/api/entities.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 276c058e6424..8840b8abdcc9 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -116,7 +116,7 @@ Example response: "description": "test-1-20150125", "id": 6, "is_shared": false, - "last_contact": "2016-01-25T16:39:48.066Z", + "contacted_at": "2016-01-25T16:39:48.066Z", "name": null, "platform": null, "projects": [ @@ -166,7 +166,7 @@ Example response: "description": "test-1-20150125-test", "id": 6, "is_shared": false, - "last_contact": "2016-01-25T16:39:48.066Z", + "contacted_at": "2016-01-25T16:39:48.066Z", "name": null, "platform": null, "projects": [ diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3908e2a83c8c..c31ef5352dd0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -380,7 +380,7 @@ class Runner < Grape::Entity class RunnerDetails < Runner expose :tag_list expose :version, :revision, :platform, :architecture - expose :contacted_at, as: :last_contact + expose :contacted_at expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } expose :projects, with: Entities::ForkedFromProject do |runner, options| if options[:current_user].is_admin? -- GitLab From acfe25edc0083b5e51cf8021b862dc1419a4006e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 22:20:09 +0100 Subject: [PATCH 33/36] Refactorize `ci_runner` factory and `let` definitions in runners API spec --- spec/factories/ci/runners.rb | 10 ++++------ spec/requests/api/runners_spec.rb | 30 ++++++++++++++---------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index db759eca9ac3..265663e84535 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -25,14 +25,12 @@ "My runner#{n}" end - platform "darwin" + platform "darwin" + is_shared false + active true - factory :ci_shared_runner do + trait :shared do is_shared true end - - factory :ci_specific_runner do - is_shared false - end end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 1a4ec44a51b0..78484747d6af 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Runners, api: true do include ApiHelpers let(:admin) { create(:user, :admin) } @@ -10,20 +10,20 @@ let(:project) { create(:project, creator_id: user.id) } let(:project2) { create(:project, creator_id: user.id) } - let!(:shared_runner) { create(:ci_shared_runner, tag_list: ['mysql', 'ruby'], active: true) } - let!(:unused_specific_runner) { create(:ci_specific_runner) } + let!(:shared_runner) { create(:ci_runner, :shared) } + let!(:unused_specific_runner) { create(:ci_runner) } let!(:specific_runner) do - runner = create(:ci_specific_runner, tag_list: ['mysql', 'ruby']) - create(:ci_runner_project, runner: runner, project: project) - runner + create(:ci_runner).tap do |runner| + create(:ci_runner_project, runner: runner, project: project) + end end let!(:two_projects_runner) do - runner = create(:ci_specific_runner) - create(:ci_runner_project, runner: runner, project: project) - create(:ci_runner_project, runner: runner, project: project2) - runner + create(:ci_runner).tap do |runner| + create(:ci_runner_project, runner: runner, project: project) + create(:ci_runner_project, runner: runner, project: project2) + end end before do @@ -352,14 +352,12 @@ end describe 'POST /projects/:id/runners' do - let(:specific_runner2) do - runner = create(:ci_specific_runner) - create(:ci_runner_project, runner: runner, project: project2) - runner - end - context 'authorized user' do it 'should enable specific runner' do + specific_runner2 = create(:ci_runner).tap do |runner| + create(:ci_runner_project, runner: runner, project: project2) + end + expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(+1) -- GitLab From 49d5c35b6948f7dd9fcae4ccfcad33d6d873c21e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 17 Feb 2016 22:56:33 +0100 Subject: [PATCH 34/36] Fix old usages of `ci_runner` factory --- spec/features/runners_spec.rb | 10 +++++----- spec/models/build_spec.rb | 6 +++--- spec/models/ci/runner_spec.rb | 14 +++++++------- spec/models/project_spec.rb | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index d97831aae14c..e8886e7edf92 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -17,10 +17,10 @@ @project3 = FactoryGirl.create :empty_project @project3.team << [user, :developer] - @shared_runner = FactoryGirl.create :ci_shared_runner - @specific_runner = FactoryGirl.create :ci_specific_runner - @specific_runner2 = FactoryGirl.create :ci_specific_runner - @specific_runner3 = FactoryGirl.create :ci_specific_runner + @shared_runner = FactoryGirl.create :ci_runner, :shared + @specific_runner = FactoryGirl.create :ci_runner + @specific_runner2 = FactoryGirl.create :ci_runner + @specific_runner3 = FactoryGirl.create :ci_runner @project.runners << @specific_runner @project2.runners << @specific_runner2 @project3.runners << @specific_runner3 @@ -84,7 +84,7 @@ before do @project = FactoryGirl.create :empty_project @project.team << [user, :master] - @specific_runner = FactoryGirl.create :ci_specific_runner + @specific_runner = FactoryGirl.create :ci_runner @project.runners << @specific_runner end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 606340d87e42..0acf6e6588a3 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -243,7 +243,7 @@ end describe :can_be_served? do - let(:runner) { FactoryGirl.create :ci_specific_runner } + let(:runner) { FactoryGirl.create :ci_runner } before { build.project.runners << runner } @@ -285,7 +285,7 @@ end context 'if there are runner' do - let(:runner) { FactoryGirl.create :ci_specific_runner } + let(:runner) { FactoryGirl.create :ci_runner } before do build.project.runners << runner @@ -322,7 +322,7 @@ it { is_expected.to be_truthy } context "and there are specific runner" do - let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago } + let(:runner) { FactoryGirl.create :ci_runner, contacted_at: 1.second.ago } before do build.project.runners << runner diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 232760dfebac..e891838672ea 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -39,7 +39,7 @@ describe :assign_to do let!(:project) { FactoryGirl.create :empty_project } - let!(:shared_runner) { FactoryGirl.create(:ci_shared_runner) } + let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) } before { shared_runner.assign_to(project) } @@ -52,15 +52,15 @@ subject { Ci::Runner.online } before do - @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago) - @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) + @runner1 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.year.ago) + @runner2 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) end it { is_expected.to eq([@runner2])} end describe :online? do - let(:runner) { FactoryGirl.create(:ci_shared_runner) } + let(:runner) { FactoryGirl.create(:ci_runner, :shared) } subject { runner.online? } @@ -84,7 +84,7 @@ end describe :status do - let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) } + let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) } subject { runner.status } @@ -115,7 +115,7 @@ describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do - runner = FactoryGirl.create(:ci_specific_runner) + runner = FactoryGirl.create(:ci_runner) project = FactoryGirl.create(:empty_project) project1 = FactoryGirl.create(:empty_project) project.runners << runner @@ -125,7 +125,7 @@ end it "returns true" do - runner = FactoryGirl.create(:ci_specific_runner) + runner = FactoryGirl.create(:ci_runner) project = FactoryGirl.create(:empty_project) project.runners << runner diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a3de23369e13..3ccb627a2596 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -519,8 +519,8 @@ describe :any_runners do let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) } - let(:specific_runner) { create(:ci_specific_runner) } - let(:shared_runner) { create(:ci_shared_runner) } + let(:specific_runner) { create(:ci_runner) } + let(:shared_runner) { create(:ci_runner, :shared) } context 'for shared runners disabled' do let(:shared_runners_enabled) { false } -- GitLab From 982368815e8e1deae9beee6c677a7fb2ac198752 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 18 Feb 2016 14:32:32 +0100 Subject: [PATCH 35/36] Add CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 4fe250efd42d..93a5203c60f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -65,6 +65,7 @@ v 8.5.0 (unreleased) - Ability to see and sort on vote count from Issues and MR lists - Fix builds scheduler when first build in stage was allowed to fail - User project limit is reached notice is hidden if the projects limit is zero + - Add API support for managing runners and project's runners v 8.4.4 - Update omniauth-saml gem to 1.4.2 -- GitLab From f2f6f77328fab308df5474a044e3205b6bd611c3 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 19 Feb 2016 13:00:36 +0100 Subject: [PATCH 36/36] Rename Entities::ForkedFromProject to Entities::BasicProjectDetails --- lib/api/entities.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c31ef5352dd0..857705dbf124 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -49,7 +49,7 @@ class ProjectHook < Hook expose :enable_ssl_verification end - class ForkedFromProject < Grape::Entity + class BasicProjectDetails < Grape::Entity expose :id expose :name, :name_with_namespace expose :path, :path_with_namespace @@ -67,7 +67,7 @@ class Project < Grape::Entity expose :shared_runners_enabled expose :creator_id expose :namespace - expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ |project, options| project.forked? } + expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } expose :avatar_url expose :star_count, :forks_count expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } @@ -382,7 +382,7 @@ class RunnerDetails < Runner expose :version, :revision, :platform, :architecture expose :contacted_at expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } - expose :projects, with: Entities::ForkedFromProject do |runner, options| + expose :projects, with: Entities::BasicProjectDetails do |runner, options| if options[:current_user].is_admin? runner.projects else -- GitLab