Commit 70783069 authored by Vasilii Iakliushin's avatar Vasilii Iakliushin

Restore TagsFinder class interface

Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/299529

**Problem**

TagsFinder interface is not compatible with other Finders (like
BranchesFinder, Repositories::TreeFinder). It prevents us from using a
keyset pagination classes.

**Solution**

* Restore TagsFinder interface to return a list of tags.
* Raise an exception when Gitaly is not available
* Handle TagsFinder exceptions
* Return 503 error for tags API endpoint when Gitaly is unavailable

Changelog: changed
parent 0397b7e9
...@@ -14,18 +14,24 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -14,18 +14,24 @@ class Projects::TagsController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def index def index
params[:sort] = params[:sort].presence || sort_value_recently_updated begin
params[:sort] = params[:sort].presence || sort_value_recently_updated
@sort = params[:sort] @sort = params[:sort]
@tags, @tags_loading_error = TagsFinder.new(@repository, params).execute @tags = TagsFinder.new(@repository, params).execute
@tags = Kaminari.paginate_array(@tags).page(params[:page]) @tags = Kaminari.paginate_array(@tags).page(params[:page])
tag_names = @tags.map(&:name) tag_names = @tags.map(&:name)
@tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names) @tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names)
@releases = project.releases.where(tag: tag_names) @releases = project.releases.where(tag: tag_names)
@tag_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, @repository, current_user, @tags).execute @tag_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, @repository, current_user, @tags).execute
rescue Gitlab::Git::CommandError => e
@tags = []
@tags_loading_error = e
end
respond_to do |format| respond_to do |format|
status = @tags_loading_error ? :service_unavailable : :ok status = @tags_loading_error ? :service_unavailable : :ok
......
...@@ -293,7 +293,11 @@ class ProjectsController < Projects::ApplicationController ...@@ -293,7 +293,11 @@ class ProjectsController < Projects::ApplicationController
end end
if find_tags && @repository.tag_count.nonzero? if find_tags && @repository.tag_count.nonzero?
tags, _ = TagsFinder.new(@repository, params).execute tags = begin
TagsFinder.new(@repository, params).execute
rescue Gitlab::Git::CommandError
[]
end
options['Tags'] = tags.take(100).map(&:name) options['Tags'] = tags.take(100).map(&:name)
end end
......
...@@ -8,8 +8,6 @@ class TagsFinder < GitRefsFinder ...@@ -8,8 +8,6 @@ class TagsFinder < GitRefsFinder
def execute def execute
tags = repository.tags_sorted_by(sort) tags = repository.tags_sorted_by(sort)
[by_search(tags), nil] by_search(tags)
rescue Gitlab::Git::CommandError => e
[[], e]
end end
end end
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
use :pagination use :pagination
end end
get ':id/repository/tags', feature_category: :source_code_management do get ':id/repository/tags', feature_category: :source_code_management do
tags, _ = ::TagsFinder.new(user_project.repository, tags = ::TagsFinder.new(user_project.repository,
sort: "#{params[:order_by]}_#{params[:sort]}", sort: "#{params[:order_by]}_#{params[:sort]}",
search: params[:search]).execute search: params[:search]).execute
...@@ -35,6 +35,9 @@ module API ...@@ -35,6 +35,9 @@ module API
else else
present paginated_tags, with: Entities::Tag, project: user_project present paginated_tags, with: Entities::Tag, project: user_project
end end
rescue Gitlab::Git::CommandError
service_unavailable!
end end
desc 'Get a single repository tag' do desc 'Get a single repository tag' do
......
...@@ -25,7 +25,7 @@ RSpec.describe Projects::TagsController do ...@@ -25,7 +25,7 @@ RSpec.describe Projects::TagsController do
with_them do with_them do
it 'returns 503 status code' do it 'returns 503 status code' do
expect_next_instance_of(TagsFinder) do |finder| expect_next_instance_of(TagsFinder) do |finder|
expect(finder).to receive(:execute).and_return([[], Gitlab::Git::CommandError.new]) expect(finder).to receive(:execute).and_raise(Gitlab::Git::CommandError)
end end
get :index, params: { namespace_id: project.namespace.to_param, project_id: project }, format: format get :index, params: { namespace_id: project.namespace.to_param, project_id: project }, format: format
......
...@@ -1143,6 +1143,22 @@ RSpec.describe ProjectsController do ...@@ -1143,6 +1143,22 @@ RSpec.describe ProjectsController do
expect(json_response["Commits"]).to include("123456") expect(json_response["Commits"]).to include("123456")
end end
context 'when gitaly is unavailable' do
before do
expect_next_instance_of(TagsFinder) do |finder|
allow(finder).to receive(:execute).and_raise(Gitlab::Git::CommandError)
end
end
it 'gets an empty list of tags' do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
expect(json_response["Branches"]).to include("master")
expect(json_response["Tags"]).to eq([])
expect(json_response["Commits"]).to include("123456")
end
end
context "when preferred language is Japanese" do context "when preferred language is Japanese" do
before do before do
user.update!(preferred_language: 'ja') user.update!(preferred_language: 'ja')
......
...@@ -8,12 +8,7 @@ RSpec.describe TagsFinder do ...@@ -8,12 +8,7 @@ RSpec.describe TagsFinder do
let_it_be(:repository) { project.repository } let_it_be(:repository) { project.repository }
def load_tags(params) def load_tags(params)
tags_finder = described_class.new(repository, params) described_class.new(repository, params).execute
tags, error = tags_finder.execute
expect(error).to eq(nil)
tags
end end
describe '#execute' do describe '#execute' do
...@@ -102,14 +97,12 @@ RSpec.describe TagsFinder do ...@@ -102,14 +97,12 @@ RSpec.describe TagsFinder do
end end
context 'when Gitaly is unavailable' do context 'when Gitaly is unavailable' do
it 'returns empty list of tags' do it 'raises an exception' do
expect(Gitlab::GitalyClient).to receive(:call).and_raise(GRPC::Unavailable) expect(Gitlab::GitalyClient).to receive(:call).and_raise(GRPC::Unavailable)
tags_finder = described_class.new(repository, {}) tags_finder = described_class.new(repository, {})
tags, error = tags_finder.execute
expect(error).to be_a(Gitlab::Git::CommandError) expect { tags_finder.execute }.to raise_error(Gitlab::Git::CommandError)
expect(tags).to eq([])
end end
end end
end end
......
...@@ -208,6 +208,20 @@ RSpec.describe API::Tags do ...@@ -208,6 +208,20 @@ RSpec.describe API::Tags do
it_behaves_like "get repository tags" it_behaves_like "get repository tags"
end end
context 'when gitaly is unavailable' do
let(:route) { "/projects/#{project_id}/repository/tags" }
before do
expect_next_instance_of(TagsFinder) do |finder|
allow(finder).to receive(:execute).and_raise(Gitlab::Git::CommandError)
end
end
it_behaves_like '503 response' do
let(:request) { get api(route, user) }
end
end
end end
describe 'GET /projects/:id/repository/tags/:tag_name' do describe 'GET /projects/:id/repository/tags/:tag_name' do
......
...@@ -76,3 +76,14 @@ RSpec.shared_examples '412 response' do ...@@ -76,3 +76,14 @@ RSpec.shared_examples '412 response' do
end end
end end
end end
RSpec.shared_examples '503 response' do
before do
# Fires the request
request
end
it 'returns 503' do
expect(response).to have_gitlab_http_status(:service_unavailable)
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