spec/chef/cookbooks/package/libraries/omnibus_helper_spec.rb (444 lines of code) (raw):
# frozen_string_literal: true
require 'chef_helper'
require 'time'
RSpec.describe OmnibusHelper do
cached(:chef_run) { converge_config }
let(:node) { chef_run.node }
let(:file) { double(:file, write: true) }
subject { described_class.new(chef_run.node) }
before do
allow(Gitlab).to receive(:[]).and_call_original
allow(File).to receive(:open).and_call_original
allow(File).to receive(:open).with('/etc/gitlab/initial_root_password', 'w', 0600).and_yield(file).once
end
describe '#user_exists?' do
it 'returns true when user exists' do
allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u root").and_return(true)
expect(subject.user_exists?('root')).to be_truthy
end
it 'returns false when user does not exist' do
allow_any_instance_of(ShellOutHelper).to receive(:success?).with("id -u nonexistentuser").and_return(false)
expect(subject.user_exists?('nonexistentuser')).to be_falsey
end
end
describe '#group_exists?' do
it 'returns true when group exists' do
allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group root").and_return(true)
expect(subject.group_exists?('root')).to be_truthy
end
it 'returns false when group does not exist' do
allow_any_instance_of(ShellOutHelper).to receive(:success?).with("getent group nonexistentgroup").and_return(false)
expect(subject.group_exists?('nonexistentgroup')).to be_falsey
end
end
describe '#not_listening?' do
let(:chef_run) { converge_config }
context 'when Redis is disabled' do
before do
stub_gitlab_rb(
redis: { enable: false }
)
end
it 'returns true when service is disabled' do
expect(subject.not_listening?('redis')).to be_truthy
end
end
context 'when Redis is enabled' do
before do
stub_gitlab_rb(
redis: { enable: true }
)
end
it 'returns true when service is down' do
stub_service_failure_status('redis', true)
expect(subject.not_listening?('redis')).to be_truthy
end
it 'returns false when service is up' do
stub_service_failure_status('redis', false)
expect(subject.not_listening?('redis')).to be_falsey
end
end
end
describe '#service_enabled?' do
context 'services are enabled' do
before do
chef_run.node.normal['gitlab']['old_service']['enable'] = true
chef_run.node.normal['new_service']['enable'] = true
chef_run.node.normal['monitoring']['another_service']['enable'] = true
end
it 'should return true' do
expect(subject.service_enabled?('old_service')).to be_truthy
expect(subject.service_enabled?('new_service')).to be_truthy
expect(subject.service_enabled?('another_service')).to be_truthy
end
end
context 'services are disabled' do
before do
chef_run.node.normal['gitlab']['old_service']['enable'] = false
chef_run.node.normal['new_service']['enable'] = false
chef_run.node.normal['monitoring']['another_service']['enable'] = false
end
it 'should return false' do
expect(subject.service_enabled?('old_service')).to be_falsey
expect(subject.service_enabled?('new_service')).to be_falsey
expect(subject.service_enabled?('another_service')).to be_falsey
end
end
end
describe '#is_managed_and_offline?' do
context 'services are disabled' do
before do
chef_run.node.normal['gitlab']['old_service']['enable'] = false
chef_run.node.normal['new_service']['enable'] = false
end
it 'returns false' do
expect(subject.is_managed_and_offline?('old_service')).to be_falsey
expect(subject.is_managed_and_offline?('new_service')).to be_falsey
end
end
context 'services are enabled' do
before do
chef_run.node.normal['gitlab']['old_service']['enable'] = true
chef_run.node.normal['new_service']['enable'] = true
end
it 'returns true when services are offline' do
stub_service_failure_status('old_service', true)
stub_service_failure_status('new_service', true)
expect(subject.is_managed_and_offline?('old_service')).to be_truthy
expect(subject.is_managed_and_offline?('new_service')).to be_truthy
end
it 'returns false when services are online ' do
stub_service_failure_status('old_service', false)
stub_service_failure_status('new_service', false)
expect(subject.is_managed_and_offline?('old_service')).to be_falsey
expect(subject.is_managed_and_offline?('new_service')).to be_falsey
end
end
end
describe '#is_deprecated_os?' do
before do
allow(OmnibusHelper).to receive(:deprecated_os_list).and_return({ "raspbian-8.0" => "GitLab 11.8" })
end
it 'detects deprecated OS correctly' do
allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "raspbian", "platform_version" => "8.0" })
OmnibusHelper.is_deprecated_os?
expect_logged_deprecation(/Your OS, raspbian-8.0, will be deprecated soon/)
end
it 'does not detects valid OS as deprecated' do
allow_any_instance_of(Ohai::System).to receive(:data).and_return({ "platform" => "ubuntu", "platform_version" => "16.04.3" })
expect(LoggingHelper).not_to receive(:deprecation)
OmnibusHelper.is_deprecated_os?
end
end
describe '#is_deprecated_praefect_config?' do
before do
chef_run.node.normal['praefect'] = config
end
context 'deprecated config' do
let(:config) do
{
storage_nodes: [
{ storage: 'praefect1', address: 'tcp://node1.internal' },
{ storage: 'praefect2', address: 'tcp://node2.internal' }
]
}
end
it 'detects deprecated config correctly' do
subject.is_deprecated_praefect_config?
expect_logged_deprecation(/Specifying Praefect storage nodes as an array is deprecated/)
end
end
context 'valid config' do
let(:config) do
{
storage_nodes: {
'praefect1' => { address: 'tcp://node1.internal' },
'praefect2' => { address: 'tcp://node2.internal' }
}
}
end
it 'does not detect a valid config as deprecated' do
expect(LoggingHelper).not_to receive(:deprecation)
subject.is_deprecated_praefect_config?
end
end
end
describe '#write_root_password' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar123',
store_initial_root_password: true
}
)
end
it 'stores root password to /etc/gitlab/initial_root_password' do
content = <<~EOS
# WARNING: This value is valid only in the following conditions
# 1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
# 2. Password hasn't been changed manually, either via UI or via command line.
#
# If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
Password: foobar123
# NOTE: This file will be automatically deleted in the first reconfigure run after 24 hours.
EOS
expect(file).to receive(:write).with(content)
described_class.new(converge_config.node).write_root_password
end
end
describe '.cleanup_root_password_file' do
context 'when /etc/gitlab/initial_root_password does not exist' do
before do
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(false)
end
it 'does not attempt to remove the file' do
expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
described_class.cleanup_root_password_file
end
end
context 'when /etc/gitlab/initial_root_password exists' do
before do
allow(File).to receive(:exist?).and_call_original
allow(File).to receive(:exist?).with('/etc/gitlab/initial_root_password').and_return(true)
allow(Time).to receive(:now).and_return(Time.parse('2021-06-07 08:45:51.377926667 +0530'))
end
context 'when file is older than 24 hours' do
before do
allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-03 06:45:51.377926667 +0530'))
end
it 'attempts to remove the file' do
expect(FileUtils).to receive(:rm_f).with('/etc/gitlab/initial_root_password')
described_class.cleanup_root_password_file
expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
end
end
context 'when file is younger than 24 hours' do
before do
allow(File).to receive(:mtime).with('/etc/gitlab/initial_root_password').and_return(Time.parse('2021-06-08 06:45:51.377926667 +0530'))
end
it 'does not attempt to remove the file' do
expect(FileUtils).not_to receive(:rm_f).with('/etc/gitlab/initial_root_password')
described_class.cleanup_root_password_file
expect_logged_note('Found old initial root password file at /etc/gitlab/initial_root_password and deleted it.')
end
end
end
end
describe '#print_root_account_details' do
context 'when not on first reconfigure after installation' do
before do
chef_run.node.normal['gitlab']['bootstrap']['enable'] = false
end
it 'does not add a note or write password to file' do
expect(LoggingHelper).not_to receive(:note)
expect(subject).not_to receive(:write_root_password)
subject.print_root_account_details
end
end
context 'when on first reconfigure after installation' do
before do
allow_any_instance_of(OmnibusHelper).to receive(:writ_root_password).and_return(true)
end
context 'when display_initial_root_password is true' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar123',
display_initial_root_password: true,
store_initial_root_password: false
}
)
end
it 'displays root password at the end of reconfigure' do
msg = <<~EOS
Default admin account has been configured with following details:
Username: root
Password: foobar123
NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
EOS
described_class.new(converge_config.node).print_root_account_details
expect_logged_note(msg)
end
end
context 'when display_initial_root_password is false' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar123',
display_initial_root_password: false,
store_initial_root_password: false
}
)
end
it 'does not display root credentials at the end of reconfigure' do
msg = <<~EOS
Default admin account has been configured with following details:
Username: root
Password: You didn't opt-in to print initial root password to STDOUT.
NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.
EOS
described_class.new(converge_config.node).print_root_account_details
expect_logged_note(msg)
end
end
describe '#write_root_password' do
context 'when store_initial_root_password is true' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar123',
display_initial_root_password: false,
store_initial_root_password: true
}
)
end
it 'writes initial root password to /etc/gitlab/initial_root_password' do
subject = described_class.new(converge_config.node)
expect(subject).to receive(:write_root_password)
subject.print_root_account_details
expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
end
end
context 'with default value for store_initial_root_password' do
context 'when password is auto-generated' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return(nil)
end
it 'writes initial root password to /etc/gitlab/initial_root_password' do
chef_run = ChefSpec::SoloRunner.converge('gitlab::default')
subject = described_class.new(chef_run.node)
expect(subject).to receive(:write_root_password)
subject.print_root_account_details
expect_logged_note(%r{Password stored to /etc/gitlab/initial_root_password})
end
end
context 'when password is specified by user' do
context 'via gitlab.rb' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar123',
}
)
end
it 'does not write initial root password to /etc/gitlab/initial_root_password' do
subject = described_class.new(converge_config.node)
expect(subject).not_to receive(:write_root_password)
subject.print_root_account_details
end
end
context 'via gitlab.rb, short password' do
before do
stub_gitlab_rb(
gitlab_rails: {
initial_root_password: 'foobar', # 6 chars, minimum is 8 chars
}
)
end
it 'raises an exception when password is too short' do
expect { described_class.new(converge_config.node) }.to raise_error(RuntimeError, /initial_root_password: Length is too short, minimum is 8 characters/)
end
end
context 'via env variable' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('GITLAB_ROOT_PASSWORD').and_return('foobar123')
end
it 'does not write initial root password to /etc/gitlab/initial_root_password' do
subject = described_class.new(converge_config.node)
expect(subject).not_to receive(:write_root_password)
subject.print_root_account_details
end
end
end
end
end
end
end
describe '#check_locale' do
let(:error_message) { "Identified encoding is not UTF-8. GitLab requires UTF-8 encoding to function properly. Please check your locale settings." }
describe 'using LC_ALL variable' do
it 'does not raise a warning when set to a UTF-8 locale even if others are not' do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_US.UTF-8')
allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
expect(LoggingHelper).not_to receive(:warning).with("Environment variable .* specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
described_class.check_locale
end
it 'raises warning when LC_ALL is non-UTF-8' do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('LC_ALL').and_return('en_SG ISO-8859-1')
described_class.check_locale
expect_logged_warning("Environment variable LC_ALL specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
describe 'using LC_CTYPE variable' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
end
it 'raises warning when LC_CTYPE is non-UTF-8' do
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_SG ISO-8859-1')
described_class.check_locale
expect_logged_warning("Environment variable LC_CTYPE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
describe 'using LC_COLLATE variable' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
end
it 'raises warning when LC_COLLATE is non-UTF-8' do
allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_SG ISO-8859-1')
described_class.check_locale
expect_logged_warning("Environment variable LC_COLLATE specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
describe 'using LANG variable' do
before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('LC_ALL').and_return(nil)
end
context 'without LC_CTYPE and LC_COLLATE' do
before do
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return(nil)
allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
end
it 'raises warning when LANG is non-UTF-8' do
allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
described_class.check_locale
expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
context 'with only LC_CTYPE set to UTF-8' do
before do
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
allow(ENV).to receive(:[]).with('LC_COLLATE').and_return(nil)
end
it 'raises warning when LANG is non-UTF-8' do
allow(ENV).to receive(:[]).with('LANG').and_return('en_SG ISO-8859-1')
described_class.check_locale
expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
context 'with both LC_CTYPE and LC_COLLATE set to UTF-8' do
before do
allow(ENV).to receive(:[]).with('LC_CTYPE').and_return('en_US.UTF-8')
allow(ENV).to receive(:[]).with('LC_COLLATE').and_return('en_US.UTF-8')
end
it 'does not raise a warning even if LANG is not UTF-8' do
described_class.check_locale
expect_logged_warning("Environment variable LANG specifies a non-UTF-8 locale. GitLab requires UTF-8 encoding to function properly. Please check your locale settings.")
end
end
end
end
describe '.resource_available?' do
cached(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
subject(:omnibus_helper) { described_class.new(chef_run.node) }
it 'returns false for a resource that exists but has not been loaded in runtime' do
expect(omnibus_helper.resource_available?('runit_service[geo-logcursor]')).to be_falsey
end
it 'returns true for a resource that exists and is loaded in runtime' do
expect(omnibus_helper.resource_available?('runit_service[logrotated]'))
end
end
end