Commit af2a185e authored by Sean McGivern's avatar Sean McGivern

Merge branch 'refactor-clone-dashboard-service' into 'master'

Refactor CloneDashboardService to remove catch/throw

See merge request gitlab-org/gitlab!27617
parents b6b8ee4e 7d69b698
...@@ -5,9 +5,18 @@ ...@@ -5,9 +5,18 @@
module Metrics module Metrics
module Dashboard module Dashboard
class CloneDashboardService < ::BaseService class CloneDashboardService < ::BaseService
include Stepable
ALLOWED_FILE_TYPE = '.yml' ALLOWED_FILE_TYPE = '.yml'
USER_DASHBOARDS_DIR = ::Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT USER_DASHBOARDS_DIR = ::Metrics::Dashboard::CustomDashboardService::DASHBOARD_ROOT
steps :check_push_authorized,
:check_branch_name,
:check_file_type,
:check_dashboard_template,
:create_file,
:refresh_repository_method_caches
class << self class << self
def allowed_dashboard_templates def allowed_dashboard_templates
@allowed_dashboard_templates ||= Set[::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH].freeze @allowed_dashboard_templates ||= Set[::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH].freeze
...@@ -22,21 +31,52 @@ module Metrics ...@@ -22,21 +31,52 @@ module Metrics
end end
end end
# rubocop:disable Cop/BanCatchThrow
def execute def execute
catch(:error) do execute_steps
throw(:error, error(_(%q(You are not allowed to push into this branch. Create another branch or open a merge request.)), :forbidden)) unless push_authorized? end
result = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute private
throw(:error, wrap_error(result)) unless result[:status] == :success
repository.refresh_method_caches([:metrics_dashboard]) def check_push_authorized(result)
success(result.merge(http_status: :created, dashboard: dashboard_details)) return error(_('You are not allowed to push into this branch. Create another branch or open a merge request.'), :forbidden) unless push_authorized?
success(result)
end end
def check_branch_name(result)
return error(_('There was an error creating the dashboard, branch name is invalid.'), :bad_request) unless valid_branch_name?
return error(_('There was an error creating the dashboard, branch named: %{branch} already exists.') % { branch: params[:branch] }, :bad_request) unless new_or_default_branch?
success(result)
end end
# rubocop:enable Cop/BanCatchThrow
private def check_file_type(result)
return error(_('The file name should have a .yml extension'), :bad_request) unless target_file_type_valid?
success(result)
end
def check_dashboard_template(result)
return error(_('Not found.'), :not_found) unless self.class.allowed_dashboard_templates.include?(params[:dashboard])
success(result)
end
def create_file(result)
create_file_response = ::Files::CreateService.new(project, current_user, dashboard_attrs).execute
if create_file_response[:status] == :success
success(result.merge(create_file_response))
else
wrap_error(create_file_response)
end
end
def refresh_repository_method_caches(result)
repository.refresh_method_caches([:metrics_dashboard])
success(result.merge(http_status: :created, dashboard: dashboard_details))
end
def dashboard_attrs def dashboard_attrs
{ {
...@@ -62,26 +102,13 @@ module Metrics ...@@ -62,26 +102,13 @@ module Metrics
Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch) Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch)
end end
# rubocop:disable Cop/BanCatchThrow
def dashboard_template def dashboard_template
@dashboard_template ||= begin @dashboard_template ||= params[:dashboard]
throw(:error, error(_('Not found.'), :not_found)) unless self.class.allowed_dashboard_templates.include?(params[:dashboard])
params[:dashboard]
end
end end
# rubocop:enable Cop/BanCatchThrow
# rubocop:disable Cop/BanCatchThrow
def branch def branch
@branch ||= begin @branch ||= params[:branch]
throw(:error, error(_('There was an error creating the dashboard, branch name is invalid.'), :bad_request)) unless valid_branch_name?
throw(:error, error(_('There was an error creating the dashboard, branch named: %{branch} already exists.') % { branch: params[:branch] }, :bad_request)) unless new_or_default_branch? # temporary validation for first UI iteration
params[:branch]
end
end end
# rubocop:enable Cop/BanCatchThrow
def new_or_default_branch? def new_or_default_branch?
!repository.branch_exists?(params[:branch]) || project.default_branch == params[:branch] !repository.branch_exists?(params[:branch]) || project.default_branch == params[:branch]
...@@ -95,20 +122,22 @@ module Metrics ...@@ -95,20 +122,22 @@ module Metrics
@new_dashboard_path ||= File.join(USER_DASHBOARDS_DIR, file_name) @new_dashboard_path ||= File.join(USER_DASHBOARDS_DIR, file_name)
end end
# rubocop:disable Cop/BanCatchThrow
def file_name def file_name
@file_name ||= begin @file_name ||= File.basename(params[:file_name])
throw(:error, error(_('The file name should have a .yml extension'), :bad_request)) unless target_file_type_valid?
File.basename(params[:file_name])
end end
end
# rubocop:enable Cop/BanCatchThrow
def target_file_type_valid? def target_file_type_valid?
File.extname(params[:file_name]) == ALLOWED_FILE_TYPE File.extname(params[:file_name]) == ALLOWED_FILE_TYPE
end end
def wrap_error(result)
if result[:message] == 'A file with this name already exists'
error(_("A file with '%{file_name}' already exists in %{branch} branch") % { file_name: file_name, branch: branch }, :bad_request)
else
result
end
end
def new_dashboard_content def new_dashboard_content
::Gitlab::Metrics::Dashboard::Processor ::Gitlab::Metrics::Dashboard::Processor
.new(project, raw_dashboard, sequence, {}) .new(project, raw_dashboard, sequence, {})
...@@ -119,14 +148,6 @@ module Metrics ...@@ -119,14 +148,6 @@ module Metrics
@repository ||= project.repository @repository ||= project.repository
end end
def wrap_error(result)
if result[:message] == 'A file with this name already exists'
error(_("A file with '%{file_name}' already exists in %{branch} branch") % { file_name: file_name, branch: branch }, :bad_request)
else
result
end
end
def raw_dashboard def raw_dashboard
YAML.safe_load(File.read(Rails.root.join(dashboard_template))) YAML.safe_load(File.read(Rails.root.join(dashboard_template)))
end end
......
...@@ -29,7 +29,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor ...@@ -29,7 +29,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
end end
context 'user does not have push right to repository' do context 'user does not have push right to repository' do
it_behaves_like 'misconfigured dashboard service response', :forbidden, %q(You are not allowed to push into this branch. Create another branch or open a merge request.) it_behaves_like 'misconfigured dashboard service response with stepable', :forbidden, 'You are not allowed to push into this branch. Create another branch or open a merge request.'
end end
context 'with rights to push to the repository' do context 'with rights to push to the repository' do
...@@ -40,19 +40,19 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor ...@@ -40,19 +40,19 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
context 'wrong target file extension' do context 'wrong target file extension' do
let(:file_name) { 'custom_dashboard.txt' } let(:file_name) { 'custom_dashboard.txt' }
it_behaves_like 'misconfigured dashboard service response', :bad_request, 'The file name should have a .yml extension' it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'The file name should have a .yml extension'
end end
context 'wrong source dashboard file' do context 'wrong source dashboard file' do
let(:dashboard) { 'config/prometheus/common_metrics_123.yml' } let(:dashboard) { 'config/prometheus/common_metrics_123.yml' }
it_behaves_like 'misconfigured dashboard service response', :not_found, 'Not found.' it_behaves_like 'misconfigured dashboard service response with stepable', :not_found, 'Not found.'
end end
context 'path traversal attack attempt' do context 'path traversal attack attempt' do
let(:dashboard) { 'config/prometheus/../database.yml' } let(:dashboard) { 'config/prometheus/../database.yml' }
it_behaves_like 'misconfigured dashboard service response', :not_found, 'Not found.' it_behaves_like 'misconfigured dashboard service response with stepable', :not_found, 'Not found.'
end end
context 'path traversal attack attempt on target file' do context 'path traversal attack attempt on target file' do
...@@ -92,7 +92,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor ...@@ -92,7 +92,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
project.repository.add_branch(user, branch, 'master') project.repository.add_branch(user, branch, 'master')
end end
it_behaves_like 'misconfigured dashboard service response', :bad_request, "There was an error creating the dashboard, branch named: existing_branch already exists." it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error creating the dashboard, branch named: existing_branch already exists.'
# temporary not available function for first iteration # temporary not available function for first iteration
# follow up issue https://gitlab.com/gitlab-org/gitlab/issues/196237 which # follow up issue https://gitlab.com/gitlab-org/gitlab/issues/196237 which
...@@ -111,7 +111,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor ...@@ -111,7 +111,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
context 'blank branch name' do context 'blank branch name' do
let(:branch) { '' } let(:branch) { '' }
it_behaves_like 'misconfigured dashboard service response', :bad_request, 'There was an error creating the dashboard, branch name is invalid.' it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error creating the dashboard, branch name is invalid.'
end end
context 'dashboard file already exists' do context 'dashboard file already exists' do
...@@ -129,7 +129,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor ...@@ -129,7 +129,7 @@ describe Metrics::Dashboard::CloneDashboardService, :use_clean_rails_memory_stor
).execute ).execute
end end
it_behaves_like 'misconfigured dashboard service response', :bad_request, "A file with 'custom_dashboard.yml' already exists in custom_dashboard branch" it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, "A file with 'custom_dashboard.yml' already exists in custom_dashboard branch"
end end
it 'extends dashboard template path to absolute url' do it 'extends dashboard template path to absolute url' do
......
...@@ -107,3 +107,14 @@ RSpec.shared_examples 'valid dashboard update process' do ...@@ -107,3 +107,14 @@ RSpec.shared_examples 'valid dashboard update process' do
service_call service_call
end end
end end
RSpec.shared_examples 'misconfigured dashboard service response with stepable' do |status_code, message = nil|
it 'returns an appropriate message and status code', :aggregate_failures do
result = service_call
expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step)
expect(result[:status]).to eq(:error)
expect(result[:http_status]).to eq(status_code)
expect(result[:message]).to eq(message) if message
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