diff --git a/files/gitlab-ctl-commands/lib/praefect.rb b/files/gitlab-ctl-commands/lib/praefect.rb new file mode 100644 index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9saWIvcHJhZWZlY3QucmI= --- /dev/null +++ b/files/gitlab-ctl-commands/lib/praefect.rb @@ -0,0 +1,89 @@ +require 'optparse' + +module Praefect + EXEC_PATH = '/opt/gitlab/embedded/bin/praefect'.freeze + DIR_PATH = '/var/opt/gitlab/praefect'.freeze + + USAGE ||= <<~EOS.freeze + Usage: + gitlab-ctl praefect command [options] + + COMMANDS: + remove-repository Remove repository from Gitaly cluster + EOS + + def self.parse_options!(args) + loop do + break if args.shift == 'praefect' + end + + global = OptionParser.new do |opts| + opts.on('-h', '--help', 'Usage help') do + Kernel.puts USAGE + Kernel.exit 0 + end + end + + options = {} + commands = { + 'remove-repository' => OptionParser.new do |opts| + opts.banner = "Usage: gitlab-ctl praefect remove-repository [options]" + + opts.on("-h", "--help", "Prints this help") do + Kernel.puts opts + Kernel.exit 0 + end + + opts.on('--virtual-storage-name NAME', 'Name of the virtual storage where the repository resides (mandatory)') do |virtual_storage_name| + options[:virtual_storage_name] = virtual_storage_name + end + + opts.on('--repository-relative-path PATH', 'Relative path to the repository on the disk (mandatory)') do |repository_relative_path| + options[:repository_relative_path] = repository_relative_path + end + + opts.on('--dir DIR', 'Directory in which Praefect is installed') do |dir| + options[:dir] = dir + end + end + } + + global.order!(args) + + command = args.shift + + raise OptionParser::ParseError, "Praefect command is not specified." \ + if command.nil? || command.empty? + + raise OptionParser::ParseError, "Unknown Praefect command: #{command}" \ + unless commands.key?(command) + + commands[command].parse!(args) + + raise OptionParser::ParseError, "Option --virtual-storage-name must be specified" \ + unless options.key?(:virtual_storage_name) + + raise OptionParser::ParseError, "Option --repository-relative-path must be specified" \ + unless options.key?(:repository_relative_path) + + options[:command] = command + options + end + + def self.execute(options) + config_file_path = File.join(options.fetch(:dir, DIR_PATH), 'config.toml') + + [EXEC_PATH, config_file_path].each do |path| + next if File.exist?(path) + + Kernel.abort "Could not find '#{path}' file. Is your package installed correctly?" + end + + command = [EXEC_PATH, "-config", config_file_path, options[:command]] + command += ["-virtual-storage", options[:virtual_storage_name]] + command += ["-repository", options[:repository_relative_path]] + + status = Kernel.system(*command) + Kernel.exit!(1) unless status + end +end diff --git a/files/gitlab-ctl-commands/praefect.rb b/files/gitlab-ctl-commands/praefect.rb new file mode 100644 index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_ZmlsZXMvZ2l0bGFiLWN0bC1jb21tYW5kcy9wcmFlZmVjdC5yYg== --- /dev/null +++ b/files/gitlab-ctl-commands/praefect.rb @@ -0,0 +1,18 @@ +require 'optparse' + +require "#{base_path}/embedded/service/omnibus-ctl/lib/gitlab_ctl" +require "#{base_path}/embedded/service/omnibus-ctl/lib/praefect" + +add_command_under_category('praefect', 'gitaly', 'Interact with Gitaly cluster', 2) do + begin + options = Praefect.parse_options!(ARGV) + rescue OptionParser::ParseError => e + warn "#{e}\n\n#{Praefect::USAGE}" + exit 128 + end + + puts "Running #{options[:command]}" + Praefect.execute(options) + puts "Done." + exit 0 +end diff --git a/spec/gitlab-ctl-commands/lib/praefect_spec.rb b/spec/gitlab-ctl-commands/lib/praefect_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..76e7a95f8f5baca58354579f61e50ec6c2e9b58d_c3BlYy9naXRsYWItY3RsLWNvbW1hbmRzL2xpYi9wcmFlZmVjdF9zcGVjLnJi --- /dev/null +++ b/spec/gitlab-ctl-commands/lib/praefect_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' +require 'optparse' + +require_relative('../../../files/gitlab-ctl-commands/lib/praefect') + +RSpec.describe Praefect do + describe '.parse_options!' do + before do + allow(Kernel).to receive(:exit) { |code| raise "Kernel.exit(#{code})" } + end + + it 'throws an error when command is not specified' do + expect { Praefect.parse_options!(%w(praefect)) }.to raise_error(OptionParser::ParseError, /Praefect command is not specified/) + end + + it 'throws an error when unknown command is specified' do + expect { Praefect.parse_options!(%w(praefect unknown-command)) }.to raise_error(OptionParser::ParseError, /Unknown Praefect command: unknown-command/) + end + + it 'throws an error when unknown option is specified' do + expect { Praefect.parse_options!(%w(praefect remove-repository --unknown)) }.to raise_error(OptionParser::InvalidOption, /unknown/) + end + + it 'throws an error when an argument for --virtual-storage-name is not specified' do + expect { Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name)) }.to raise_error(OptionParser::MissingArgument, /virtual-storage-name/) + end + + it 'throws an error when --virtual-storage-name is not specified' do + expect { Praefect.parse_options!(%w(praefect remove-repository)) }.to raise_error(OptionParser::ParseError, /Option --virtual-storage-name must be specified/) + end + + it 'throws an error when an argument for --repository-relative-path is not specified' do + expect { Praefect.parse_options!(%w(praefect remove-repository --repository-relative-path)) }.to raise_error(OptionParser::MissingArgument, /repository-relative-path/) + end + + it 'throws an error when --repository-relative-path is not specified' do + expect { Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name name)) }.to raise_error(OptionParser::ParseError, /Option --repository-relative-path must be specified/) + end + + it 'successfully parses correct params' do + expected_options = { command: 'remove-repository', virtual_storage_name: 'name', repository_relative_path: 'path' } + expect(Praefect.parse_options!(%w(praefect remove-repository --virtual-storage-name name --repository-relative-path path))).to eq(expected_options) + end + + context 'when help option is passed' do + it 'shows help message and exit for global help option' do + expect(Kernel).to receive(:puts) do |msg| + expect(msg.to_s).to match(/gitlab-ctl praefect command/) + end + + expect { Praefect.parse_options!(%w(praefect -h)) }.to raise_error('Kernel.exit(0)') + end + + ['remove-repository'].each do |cmd| + it "shows help message and exit for #{cmd} help option" do + expect(Kernel).to receive(:puts) do |msg| + expect(msg.to_s).to match(/gitlab-ctl praefect remove-repository/) + end + + expect { Praefect.parse_options!(%W(praefect #{cmd} -h)) }.to raise_error('Kernel.exit(0)') + end + end + end + end + + describe '#execute' do + context 'when package is not installed correctly' do + it 'aborts the execution if a path does not exit' do + allow(File).to receive(:exists?).twice.and_return(false) + expect(Kernel).to receive(:abort).and_raise('aborted') + + expect { Praefect.execute({}) }.to raise_error('aborted') + end + end + + it 'executes the command' do + allow(File).to receive(:exist?).and_return(true) + expect(Kernel).not_to receive(:abort) + + expect(Kernel).to receive(:system).with( + Praefect::EXEC_PATH, '-config', 'dir/config.toml', 'cmd', + '-virtual-storage', 'storage-name', '-repository', 'repository-path' + ).and_return('result!') + expect(Kernel).not_to receive(:exit!) + + options = { + virtual_storage_name: 'storage-name', + repository_relative_path: 'repository-path', + command: 'cmd', + dir: 'dir' + } + Praefect.execute(options) + end + end +end