diff --git a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb index 669191a70a82bd75789f98111a4890092d8576d0..a126968d6ec47a8fd7f965b972f6daac646d24bb 100644 --- a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb +++ b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb @@ -27,6 +27,7 @@ module Gitaly parse_git_data_dirs parse_gitaly_storages parse_gitconfig + check_duplicate_storage_paths end def gitaly_address @@ -160,6 +161,32 @@ module Gitaly Chef::Mixin::DeepMerge.deep_merge!(tmp_source_hash, Gitlab['gitaly']) end + # Validate that no storages are sharing the same path. + def check_duplicate_storage_paths + # Deep copy storages to avoid mutating the original. + storages = Marshal.load(Marshal.dump(Gitlab['gitaly']['configuration']['storage'])) + + storages.each do |storage| + storage[:realpath] = + begin + File.realpath(storage[:path]) + rescue Errno::ENOENT + storage[:path] + end + end + + realpath_duplicates = storages.group_by { |storage| storage[:realpath] }.select { |_, entries| entries.size > 1 } + + return if realpath_duplicates.empty? + + output = realpath_duplicates.map do |realpath, entries| + names = entries.map { |s| s[:name] }.join(', ') + "#{realpath}: #{names}" + end + + raise "One or more Gitaly storages are accessible by multiple filesystem paths:\n #{output.join('\n ')}" + end + private def user_config diff --git a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb index 13ec76272d1a80dd4e34c18b1533a9d1dc3d6c77..61d738128e6e940d29ee7a678d4e97f6b1ccf486 100644 --- a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb +++ b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb @@ -803,6 +803,48 @@ RSpec.describe 'gitaly' do .with_content(%r{\[\[storage\]\]\s+name = "nfs1"\s+path = "/mnt/nfs1/repositories"}) end end + + context 'with multiple storages using the same path' do + let(:real_path) { Dir.mktmpdir } + let(:other_dir) { Dir.mktmpdir } + let(:symlink_path) { File.join(other_dir, 'symlink') } + + before do + File.symlink(real_path, symlink_path) + stub_gitlab_rb( + gitaly: { + configuration: { + storage: [ + { + 'name' => 'default', + 'path' => '/var/opt/gitlab/git-data/repositories' + }, + { + 'name' => 'other', + 'path' => '/var/opt/gitlab/git-data/repositories' + }, + { + 'name' => 'realpath', + 'path' => real_path + }, + { + 'name' => 'symlinked', + 'path' => symlink_path, + } + ] + } + } + ) + end + + after do + FileUtils.rm_rf([real_path, other_dir]) + end + + it 'raises an error' do + expect { chef_run }.to raise_error(/One or more Gitaly storages are accessible by multiple filesystem paths:.*: default.*: realpath/m) + end + end end end end