spec/chef/gitlab-ctl-commands/lib/gitlab_ctl_backup_spec.rb (261 lines of code) (raw):
require 'chef_helper'
$LOAD_PATH << File.join(__dir__, '../../../files/gitlab-ctl-commands/lib')
require 'gitlab_ctl'
RSpec.describe GitlabCtl::Backup do
let(:backup_dir_path) { '/etc/gitlab/config_backup' }
# Valid backup files match the regular expression
let(:future_valid_backup_files) do
['gitlab_config_2302886428_2042_12_22.tar']
end
let(:past_valid_backup_files) do
[
'gitlab_config_1603388428_2020_10_22.tar',
'gitlab_config_1606070428_2020_11_22.tar',
'gitlab_config_1608662428_2020_12_22.tar',
]
end
let(:valid_backup_files) do
future_valid_backup_files + past_valid_backup_files
end
let(:invalid_backup_files) do
[
'lab_config_1600793789_2020_09_22.tar',
'gitlab_config_manual.tar',
'my_cool_backup.tar'
]
end
let(:all_backup_files) do
valid_backup_files + invalid_backup_files
end
let(:warning_message) { "WARNING: In GitLab 14.0 we will begin removing all configuration backups older than" }
before do
allow(GitlabCtl::Backup).to receive(:print_warning?).and_return(false)
allow(File).to receive(:exist?).and_return(true)
allow(FileUtils).to receive(:chmod)
allow(FileUtils).to receive(:chown)
allow(FileUtils).to receive(:mkdir)
allow_any_instance_of(Kernel).to receive(:system).and_return(true)
allow_any_instance_of(Kernel).to receive(:exit!)
allow(Dir).to receive(:chdir).and_yield
allow(FileUtils).to receive(:rm)
allow(Time).to receive(:now).and_return(Time.utc(2021))
# Don't let messages output during test
allow(STDOUT).to receive(:write)
end
context 'with default settings' do
let(:options) { { delete_old_backups: nil } }
before do
allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
end
it 'should default to deleting old backups' do
backup = GitlabCtl::Backup.new
expect(backup.wants_pruned).to eq(true)
end
context 'when the backup path is readable by non-root' do
before do
allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(false)
end
it 'should warn the administrator' do
expect { GitlabCtl::Backup.perform }.to output(/WARNING: #{backup_dir_path} may be read by non-root users/).to_stderr
end
end
context 'when the backup path is readable by only root' do
let(:archive_name) { 'gitlab_config_8675309_1981_11_16.tar' }
before do
allow_any_instance_of(GitlabCtl::Backup).to receive(:secure?).and_return(true)
allow_any_instance_of(GitlabCtl::Backup).to receive(:archive_name).and_return(archive_name)
end
it 'should use proper tar command' do
expect_any_instance_of(Kernel).to receive(:system).with(
*%W(tar --absolute-names --dereference --verbose --create --file /etc/gitlab/config_backup/#{archive_name}
--exclude /etc/gitlab/config_backup -- /etc/gitlab)
)
GitlabCtl::Backup.perform
end
it 'should set proper file mode on archive file' do
expect(FileUtils).to receive(:chmod).with(0600, %r{#{backup_dir_path}/#{archive_name}})
GitlabCtl::Backup.perform
end
it 'should notify about archive creation starting' do
expect { GitlabCtl::Backup.perform }.to output(
%r{Creating configuration backup archive: #{archive_name}}
).to_stdout
end
it 'should put notify about archive creation completion' do
expect { GitlabCtl::Backup.perform }.to output(
%r{Configuration backup archive complete: #{backup_dir_path}/#{archive_name}}
).to_stdout
end
it 'should be able to accept a backup directory path argument' do
custom_dir = "/TanukisOfUnusualSize"
options = { backup_path: custom_dir }
expect { GitlabCtl::Backup.perform(options) }.to output(
%r{Configuration backup archive complete: #{custom_dir}/#{archive_name}}
).to_stdout
end
context 'when etc backup path does not exist' do
before do
allow(File).to receive(:exist?).with(backup_dir_path).and_return(false)
end
it 'should log proper message' do
expect { GitlabCtl::Backup.perform }.to output(
%r{Could not find '#{backup_dir_path}' directory\. Creating\.}).to_stdout
end
it 'should create directory' do
expect(FileUtils).to receive(:mkdir).with(backup_dir_path, mode: 0700)
GitlabCtl::Backup.perform
end
it 'should set proper owner and group' do
expect(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path)
GitlabCtl::Backup.perform
end
context 'when /etc/gitlab is NFS share' do
before do
allow(STDERR).to receive(:write)
allow(FileUtils).to receive(:chown).with('root', 'root', backup_dir_path).and_raise(Errno::EPERM)
end
it 'should put proper output to STDERR' do
expect { GitlabCtl::Backup.perform }.to output(
/Warning: Could not change owner of #{backup_dir_path} to 'root:root'. As a result your backups may be accessible to some non-root users./).to_stderr
end
end
end
end
context 'when etc path does not exist' do
let(:etc_path) { '/etc/gitlab' }
before do
allow(File).to receive(:exist?).with(etc_path).and_return(false)
end
it "should abort with proper message" do
expect { GitlabCtl::Backup.perform }.to output(/Could not find '#{etc_path}' directory. Is your package installed correctly?/).to_stderr.and raise_error
end
end
end
context 'when backup_keep_time is non-zero' do
let(:options) { { delete_old_backups: true } }
let(:node_config) do
{
'gitlab' => {
'gitlab-rails' => {
'backup_keep_time' => 1
}
}
}
end
before do
allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
@backup = GitlabCtl::Backup.new(options)
end
context 'when no valid files exist' do
before do
allow(Dir).to receive(:glob).and_return(invalid_backup_files)
allow_any_instance_of(Kernel).to receive(:warn)
end
it 'should find no files to remove' do
expect(@backup.removable_archives.length).to equal(0)
expect(FileUtils).not_to have_received(:rm)
expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
end
end
context 'when files exist to remove' do
before do
allow(Dir).to receive(:glob).and_return(all_backup_files)
end
it 'should identify the correct files to remove' do
removed = past_valid_backup_files.map do |x|
File.join(backup_dir_path, x)
end
expect(@backup.removable_archives).to match_array(removed)
@backup.remove_backups
removed.each do |f|
expect(FileUtils).to have_received(:rm).with(f)
end
end
context 'when file removal fails' do
let(:failed_file) { File.join(backup_dir_path, past_valid_backup_files[1]) }
let(:message) { "Permission denied @ unlink_internal - #{failed_file}" }
let(:removed_files) do
files = past_valid_backup_files.map do |f|
File.join(backup_dir_path, f)
end
files.select! { |f| f != failed_file }
end
before do
allow(FileUtils).to receive(:rm).with(failed_file).and_raise(Errno::EACCES, message)
end
it 'removes the remaining expected files' do
allow_any_instance_of(Kernel).to receive(:warn)
@backup.remove_backups
removed_files.each do |filename|
expect(FileUtils).to have_received(:rm).with(filename)
end
end
it 'sets the proper file removal count' do
allow_any_instance_of(Kernel).to receive(:warn)
expect { @backup.remove_backups }.to output(/done. Removed #{removed_files.length} older configuration backups./).to_stdout
end
it 'prints the error from file that could not be removed' do
expect { @backup.remove_backups }.to output(a_string_matching(message)).to_stderr
end
end
end
context 'when the only valid files are after backup_keep_time' do
before do
allow(Dir).to receive(:glob).and_return(future_valid_backup_files)
end
it 'should find no files to remove' do
allow_any_instance_of(Kernel).to receive(:warn)
expect(@backup.removable_archives.length).to equal(0)
expect(FileUtils).not_to have_received(:rm)
expect { @backup.prune }.to output(/done. Removed 0 older configuration backups./).to_stdout
end
end
end
context 'when backup_keep_time is zero' do
let(:options) { { delete_old_backups: nil } }
let(:node_config) do
{
'gitlab' => {
'gitlab-rails' => {
'backup_keep_time' => 0
}
}
}
end
before do
allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return(node_config)
allow(GitlabCtl::Backup).to receive(:secure?).and_return(true)
allow(Dir).to receive(:glob).and_return(all_backup_files)
@backup = GitlabCtl::Backup.new(options)
end
it 'should skip performing a backup' do
expect { @backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
expect(FileUtils).not_to have_received(:rm)
end
end
context 'when node attributes invocation fails' do
let(:options) { { delete_old_backups: nil } }
before do
allow(GitlabCtl::Util).to receive(:get_node_attributes).and_raise(GitlabCtl::Errors::NodeError, "Oh no, node failed")
allow_any_instance_of(Kernel).to receive(:warn)
allow(Dir).to receive(:glob).and_return(all_backup_files)
end
it 'should not remove old archives' do
backup = GitlabCtl::Backup.new(options)
expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
expect(FileUtils).not_to have_received(:rm)
end
end
context 'when node attributes returns an empty hash' do
let(:options) { { delete_old_backups: nil } }
before do
allow(GitlabCtl::Util).to receive(:get_node_attributes).and_return({})
allow_any_instance_of(Kernel).to receive(:warn)
allow(Dir).to receive(:glob).and_return(all_backup_files)
end
it 'should not remove old archives' do
backup = GitlabCtl::Backup.new(options)
expect { backup.prune }.to output(/Keeping all older configuration backups/).to_stdout
expect(FileUtils).not_to have_received(:rm)
end
end
end