diff options
-rw-r--r-- | lib/api/runners.rb | 26 | ||||
-rw-r--r-- | spec/requests/api/runners_spec.rb | 102 |
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 |