summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/api/runners.rb26
-rw-r--r--spec/requests/api/runners_spec.rb102
2 files changed, 128 insertions, 0 deletions
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 7d4ca3f1587..87b37c05397 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -59,6 +59,26 @@ module 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 @@ module API
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 e5c837d051b..9ffa59dac07 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -9,6 +9,7 @@ describe API::API, api: true do
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 @@ describe API::API, api: true do
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