Commit 29fbf961 authored by Vitali Tatarintev's avatar Vitali Tatarintev Committed by Fabio Pitino

Create Backend Services for Cancel and Unschedule job actions

parent 3a46ab2e
...@@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_read_build_trace!, only: [:trace, :raw] before_action :authorize_read_build_trace!, only: [:trace, :raw]
before_action :authorize_read_build! before_action :authorize_read_build!
before_action :authorize_update_build!, before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :erase] except: [:index, :show, :status, :raw, :trace, :erase, :cancel, :unschedule]
before_action :authorize_erase_build!, only: [:erase] before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :verify_api_request!, only: :terminal_websocket_authorize
...@@ -93,22 +93,28 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -93,22 +93,28 @@ class Projects::JobsController < Projects::ApplicationController
end end
def cancel def cancel
return respond_422 unless @build.cancelable? service_response = Ci::BuildCancelService.new(@build, current_user).execute
@build.cancel if service_response.success?
destination = continue_params[:to].presence || builds_project_pipeline_path(@project, @build.pipeline.id)
if continue_params[:to] redirect_to destination
redirect_to continue_params[:to] elsif service_response.http_status == :forbidden
access_denied!
else else
redirect_to builds_project_pipeline_path(@project, @build.pipeline.id) head service_response.http_status
end end
end end
def unschedule def unschedule
return respond_422 unless @build.scheduled? service_response = Ci::BuildUnscheduleService.new(@build, current_user).execute
@build.unschedule! if service_response.success?
redirect_to build_path(@build) redirect_to build_path(@build)
elsif service_response.http_status == :forbidden
access_denied!
else
head service_response.http_status
end
end end
def status def status
......
# frozen_string_literal: true
module Ci
class BuildCancelService
def initialize(build, user)
@build = build
@user = user
end
def execute
return forbidden unless allowed?
return unprocessable_entity unless build.cancelable?
build.cancel
ServiceResponse.success(payload: build)
end
private
attr_reader :build, :user
def allowed?
user.can?(:update_build, build)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def unprocessable_entity
ServiceResponse.error(message: 'Unprocessable entity', http_status: :unprocessable_entity)
end
end
end
# frozen_string_literal: true
module Ci
class BuildUnscheduleService
def initialize(build, user)
@build = build
@user = user
end
def execute
return forbidden unless allowed?
return unprocessable_entity unless build.scheduled?
build.unschedule!
ServiceResponse.success(payload: build)
end
private
attr_reader :build, :user
def allowed?
user.can?(:update_build, build)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def unprocessable_entity
ServiceResponse.error(message: 'Unprocessable entity', http_status: :unprocessable_entity)
end
end
end
...@@ -868,64 +868,85 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -868,64 +868,85 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end end
describe 'POST cancel' do describe 'POST cancel' do
before do context 'when user is authorized to cancel the build' do
project.add_developer(user) before do
sign_in(user) project.add_developer(user)
end sign_in(user)
end
context 'when continue url is present' do context 'when continue url is present' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
context 'when continue to is a safe url' do context 'when continue to is a safe url' do
let(:url) { '/test' } let(:url) { '/test' }
before do before do
post_cancel(continue: { to: url }) post_cancel(continue: { to: url })
end end
it 'redirects to the continue url' do it 'redirects to the continue url' do
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(url) expect(response).to redirect_to(url)
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end end
it 'transits to canceled' do context 'when continue to is not a safe url' do
expect(job.reload).to be_canceled let(:url) { 'http://example.com' }
it 'raises an error' do
expect { cancel_with_redirect(url) }.to raise_error
end
end end
end end
context 'when continue to is not a safe url' do context 'when continue url is not present' do
let(:url) { 'http://example.com' } before do
post_cancel
end
context 'when job is cancelable' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'redirects to the builds page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id))
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end
context 'when job is not cancelable' do
let(:job) { create(:ci_build, :canceled, pipeline: pipeline) }
it 'raises an error' do it 'returns unprocessable_entity' do
expect { cancel_with_redirect(url) }.to raise_error expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end end
end end
end end
context 'when continue url is not present' do context 'when user is not authorized to cancel the build' do
let!(:job) { create(:ci_build, :cancelable, pipeline: pipeline) }
before do before do
project.add_reporter(user)
sign_in(user)
post_cancel post_cancel
end end
context 'when job is cancelable' do it 'responds with not_found' do
let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } expect(response).to have_gitlab_http_status(:not_found)
it 'redirects to the builds page' do
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id))
end
it 'transits to canceled' do
expect(job.reload).to be_canceled
end
end end
context 'when job is not cancelable' do it 'does not transit to canceled' do
let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } expect(job.reload).not_to be_canceled
it 'returns unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end end
end end
...@@ -938,43 +959,60 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -938,43 +959,60 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
describe 'POST unschedule' do describe 'POST unschedule' do
before do before do
project.add_developer(user) create(:protected_branch, :developers_can_merge, name: 'master', project: project)
end
create(:protected_branch, :developers_can_merge, context 'when user is authorized to unschedule the build' do
name: 'master', project: project) before do
project.add_developer(user)
sign_in(user)
sign_in(user) post_unschedule
end
post_unschedule context 'when job is scheduled' do
end let(:job) { create(:ci_build, :scheduled, pipeline: pipeline) }
context 'when job is scheduled' do it 'redirects to the unscheduled job page' do
let(:job) { create(:ci_build, :scheduled, pipeline: pipeline) } expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(namespace_project_job_path(id: job.id))
end
it 'redirects to the unscheduled job page' do it 'transits to manual' do
expect(response).to have_gitlab_http_status(:found) expect(job.reload).to be_manual
expect(response).to redirect_to(namespace_project_job_path(id: job.id)) end
end end
it 'transits to manual' do context 'when job is not scheduled' do
expect(job.reload).to be_manual let(:job) { create(:ci_build, pipeline: pipeline) }
it 'renders unprocessable_entity' do
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end end
end end
context 'when job is not scheduled' do context 'when user is not authorized to unschedule the build' do
let(:job) { create(:ci_build, pipeline: pipeline) } let(:job) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'renders unprocessable_entity' do before do
expect(response).to have_gitlab_http_status(:unprocessable_entity) project.add_reporter(user)
sign_in(user)
post_unschedule
end
it 'responds with not_found' do
expect(response).to have_gitlab_http_status(:not_found)
end
it 'does not transit to scheduled' do
expect(job.reload).not_to be_manual
end end
end end
def post_unschedule def post_unschedule
post :unschedule, params: { post :unschedule, params: { namespace_id: project.namespace, project_id: project, id: job.id }
namespace_id: project.namespace,
project_id: project,
id: job.id
}
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildCancelService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
describe '#execute' do
subject(:execute) { described_class.new(build, user).execute }
context 'when user is authorized to cancel the build' do
before do
project.add_maintainer(user)
end
context 'when build is cancelable' do
let!(:build) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'transits build to canceled', :aggregate_failures do
response = execute
expect(response).to be_success
expect(response.payload.reload).to be_canceled
end
end
context 'when build is not cancelable' do
let!(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
it 'responds with unprocessable entity', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:unprocessable_entity)
end
end
end
context 'when user is not authorized to cancel the build' do
let!(:build) { create(:ci_build, :cancelable, pipeline: pipeline) }
it 'responds with forbidden', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:forbidden)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildUnscheduleService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
describe '#execute' do
subject(:execute) { described_class.new(build, user).execute }
context 'when user is authorized to unschedule the build' do
before do
project.add_maintainer(user)
end
context 'when build is scheduled' do
let!(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'transits build to manual' do
response = execute
expect(response).to be_success
expect(response.payload.reload).to be_manual
end
end
context 'when build is not scheduled' do
let!(:build) { create(:ci_build, pipeline: pipeline) }
it 'responds with unprocessable entity', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:unprocessable_entity)
end
end
end
context 'when user is not authorized to unschedule the build' do
let!(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
it 'responds with forbidden', :aggregate_failures do
response = execute
expect(response).to be_error
expect(response.http_status).to eq(:forbidden)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment