spec/gitlab/qa/component/gitlab_spec.rb (489 lines of code) (raw):
# frozen_string_literal: true
require 'pry'
module Gitlab
module QA
describe Component::Gitlab do
subject(:gitlab_component) { described_class.new }
around do |example|
ClimateControl.modify(QA_LOG_PATH: artifacts_dir) { example.run }
end
before do
Runtime::Scenario.define(:omnibus_configuration, Runtime::OmnibusConfiguration.new)
Runtime::Scenario.define(:seed_db, false)
Runtime::Scenario.define(:seed_admin_token, true)
Runtime::Scenario.define(:omnibus_exec_commands, [])
Runtime::Scenario.define(:skip_server_hooks, true)
end
let(:full_ce_address) { 'registry.gitlab.com/foo/gitlab/gitlab-ce' }
let(:full_ce_address_with_complex_tag) { "#{full_ce_address}:omnibus-7263a2" }
let(:artifacts_dir) { '/tmp/gitlab-qa/gitlab-qa-run-2018-07-11-10-00-00-abc123' }
describe '#release' do
context 'with no release' do
it 'defaults to CE' do
expect(gitlab_component.release.to_s).to eq 'gitlab/gitlab-ce:nightly'
end
end
end
describe '#release=' do
before do
gitlab_component.release = release
end
context 'when release is a Release object' do
let(:release) { create_release('CE') }
it 'returns a correct release' do
expect(gitlab_component.release.to_s).to eq 'gitlab/gitlab-ce:nightly'
end
end
context 'when release is a string' do
context 'with a simple tag' do
let(:release) { full_ce_address_with_complex_tag }
it 'returns a correct release' do
expect(gitlab_component.release.to_s).to eq full_ce_address_with_complex_tag
end
end
end
end
describe '#name' do
before do
gitlab_component.release = create_release('EE')
end
it 'returns a unique name' do
expect(gitlab_component.name).to match(/\Agitlab-ee-(\w+){8}\z/)
end
end
describe '#hostname' do
it { expect(gitlab_component.hostname).to match(/\Agitlab-ce-(\w+){8}\.\z/) }
context 'with a network' do
before do
gitlab_component.network = 'local'
end
it 'returns a valid hostname' do
expect(gitlab_component.hostname).to match(/\Agitlab-ce-(\w+){8}\.local\z/)
end
end
end
describe '#address' do
context 'with a network' do
before do
gitlab_component.network = 'local'
end
it 'returns a HTTP address' do
expect(gitlab_component.address)
.to match(%r{http://gitlab-ce-(\w+){8}\.local\z})
end
end
end
describe '#start' do
let(:docker) { spy('docker command') }
before do
stub_const('Gitlab::QA::Docker::Command', docker)
allow(gitlab_component).to receive(:ensure_configured!)
end
it 'runs a docker command' do
gitlab_component.start
expect(docker).to have_received(:execute!)
end
it 'dynamically binds HTTP port' do
gitlab_component.start
expect(docker).to have_received(:port).with("80")
end
it 'specifies the name' do
gitlab_component.start
expect(docker).to have_received(:<<)
.with("--name #{gitlab_component.name}")
end
it 'specifies the hostname' do
gitlab_component.start
expect(docker).to have_received(:<<)
.with("--hostname #{gitlab_component.hostname}")
end
it 'bind-mounds volume with logs in an appropriate directory' do
allow(Gitlab::QA::Runtime::Env)
.to receive(:host_artifacts_dir)
.and_return(artifacts_dir)
gitlab_component.name = 'my-gitlab'
gitlab_component.start
expect(docker).to have_received(:volume)
.with("#{artifacts_dir}/my-gitlab/logs", '/var/log/gitlab', 'Z')
end
context 'with a network' do
before do
gitlab_component.network = 'testing-network'
end
it 'specifies the network' do
gitlab_component.start
expect(docker).to have_received(:<<)
.with('--net testing-network')
end
end
context 'with volumes' do
before do
gitlab_component.volumes = { '/from' => '/to' }
end
it 'adds --volume switches to the command' do
gitlab_component.start
expect(docker).to have_received(:volume)
.with('/from', '/to', 'Z')
end
end
context 'with environment' do
before do
gitlab_component.environment = { 'TEST' => 'some value' }
end
it 'adds environment variables to the command' do
gitlab_component.start
expect(docker).to have_received(:env)
.with('TEST', 'some value')
end
end
context 'with network_alias' do
before do
gitlab_component.add_network_alias('lolcathost')
end
it 'adds --network-alias switches to the command' do
gitlab_component.start
expect(docker).to have_received(:<<).with('--network-alias lolcathost')
end
end
describe 'with tls cert volumes' do
let(:cert_path) { File.expand_path('../../../../tls_certificates', __dir__) }
let(:alpine_helper) do
instance_double(
Gitlab::QA::Component::Alpine,
:volumes= => nil,
:start_instance => nil,
:name => 'alpine',
:teardown! => nil
)
end
let(:cert_volumes) do
{
'authority' => '/etc/gitlab/trusted-certs',
'gitlab-ssl' => '/etc/gitlab/ssl'
}
end
before do
allow(Gitlab::QA::Component::Alpine).to receive(:perform).and_yield(alpine_helper)
end
it 'creates volumes with tls certs', :aggregate_failures do
gitlab_component.prepare
expect(alpine_helper).to have_received(:volumes=).with(cert_volumes)
expect(alpine_helper).to have_received(:start_instance)
expect(alpine_helper).to have_received(:teardown!)
expect(docker).to have_received(:execute)
.with("cp #{cert_path}/authority/. alpine:#{cert_volumes['authority']}").once
expect(docker).to have_received(:execute)
.with("cp #{cert_path}/gitlab/. alpine:#{cert_volumes['gitlab-ssl']}").once
end
end
end
describe '#seed_db' do
let(:expect_empty) { false }
let(:file_patterns) { nil }
let(:docker_engine) { spy('docker engine') }
let(:exec_commands) { [] }
let(:seed_db_dir) do
dir = File.expand_path("/tmp/gitlab-qa-gitlab-rb-spec-#{SecureRandom.hex(10)}", __dir__)
FileUtils.mkdir_p(dir)
FileUtils.touch("#{dir}/test_file1.rb")
FileUtils.touch("#{dir}/test_file2.rb")
FileUtils.touch("#{dir}/file3.rb")
dir
end
before do
stub_const('Gitlab::QA::Docker::Engine', docker_engine)
stub_const('Gitlab::QA::Component::Gitlab::DATA_SEED_PATH', seed_db_dir)
stub_const('Gitlab::QA::Component::Gitlab::DATA_PATH', '/d/e/f')
gitlab_component.instance_variable_set(:@exec_commands, exec_commands)
gitlab_component.instance_variable_set(:@seed_admin_token, false)
gitlab_component.instance_variable_set(:@seed_db, seed_db)
allow(Runtime::Scenario).to receive(:seed_db).and_return(file_patterns)
gitlab_component.process_exec_commands
end
context 'when seed_db is true' do
let(:seed_db) { true }
shared_examples 'exec docker commands when instance is ready' do
it 'copies the data seed path to data path' do
expect(docker_engine).to have_received(:copy).with(gitlab_component.name, seed_db_dir, '/d/e/f')
end
it 'adds the seed test data command to the exec_commands' do
expected_commands = gitlab_component.send(:seed_test_data_command)
expect(expected_commands).not_to be_empty unless expect_empty
expect(exec_commands).to include(expected_commands)
end
end
context 'with duplicated search pattern' do
let(:file_patterns) { %w[test*.rb test_file1.rb] }
it_behaves_like 'exec docker commands when instance is ready'
end
context 'with all seed scripts' do
let(:file_patterns) { ['*'] }
it_behaves_like 'exec docker commands when instance is ready'
end
context 'without matches' do
let(:file_patterns) { ['test_file1'] }
let(:expect_empty) { true }
it_behaves_like 'exec docker commands when instance is ready'
end
end
context 'when `--seed-db` is not set' do
let(:seed_db) { false }
let(:file_patterns) { ['file3.rb'] }
it 'does not execute the seed_test_data script' do
expect(gitlab_component).not_to receive(:seed_test_data_command)
end
end
end
describe '#seed_admin_token' do
let(:docker_engine) { spy('docker engine') }
let(:exec_commands) { [] }
before do
stub_const('Gitlab::QA::Docker::Engine', docker_engine)
stub_const('Gitlab::QA::Component::Gitlab::DATA_SEED_PATH', '/a/b/c')
stub_const('Gitlab::QA::Component::Gitlab::DATA_PATH', '/d/e/f')
gitlab_component.instance_variable_set(:@seed_admin_token, seed_admin_token)
gitlab_component.instance_variable_set(:@exec_commands, exec_commands)
gitlab_component.process_exec_commands
end
context 'when seed_admin_token is true' do
let(:seed_admin_token) { true }
it 'copies the data seed path to data pat' do
expect(docker_engine).to have_received(:copy).with(gitlab_component.name, '/a/b/c', '/d/e/f')
end
it 'adds the seed admin token command to the exec_commands' do
expect(exec_commands).to include(gitlab_component.send(:seed_admin_token_command))
end
end
context 'when seed_admin_token is false' do
let(:seed_admin_token) { false }
it 'does not copy the data seed path to data path' do
expect(docker_engine).not_to have_received(:copy)
end
it 'does not add the seed admin token command to the exec_commands' do
expect(exec_commands).not_to include(gitlab_component.send(:seed_admin_token_command))
end
end
end
describe '#teardown' do
let(:docker_engine) { spy('docker engine') }
before do
stub_const('Gitlab::QA::Docker::Engine', docker_engine)
end
context 'when `--no-teardown` is set' do
it 'leaves containers running' do
allow(gitlab_component).to receive(:teardown?).and_return(false)
gitlab_component.teardown
expect(docker_engine).not_to have_received(:stop)
expect(docker_engine).not_to have_received(:remove)
expect(docker_engine).to have_received(:ps)
end
end
context 'when `--no-teardown` is not set' do
it 'stops and removes containers' do
allow(gitlab_component).to receive(:teardown?).and_return(true)
allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir)
file_double = instance_double(File)
allow(file_double).to receive(:sync=)
allow(File).to receive(:open).with("#{artifacts_dir}/pg_stats.log", "a").and_return(file_double)
allow(File).to receive(:open).with("qa/log/path/gitlab-qa.log", 9).and_return(file_double)
gitlab_component.teardown
expect(docker_engine).to have_received(:remove)
expect(docker_engine).not_to have_received(:ps)
end
end
end
describe '#server_hooks' do
let(:docker_engine) { spy('docker engine') }
before do
stub_const('Gitlab::QA::Docker::Engine', docker_engine)
gitlab_component.instance_variable_set(:@skip_server_hooks, skip_server_hooks)
end
context 'when skip_server_hooks is false' do
let(:skip_server_hooks) { false }
it 'adds git server hooks to exec_commands' do
expect(Support::ConfigScripts).to receive(:add_git_server_hooks).with(docker_engine, gitlab_component.name)
gitlab_component.process_exec_commands
end
end
context 'when skip_server_hooks is true' do
let(:skip_server_hooks) { true }
it 'does not add git server hooks to exec_commands' do
expect(Support::ConfigScripts).not_to receive(:add_git_server_hooks)
gitlab_component.process_exec_commands
end
end
end
describe '#reconfigure' do
let(:docker) { spy('docker') }
context 'with default omnibus configuration' do
before do
stub_const('Gitlab::QA::Support::ShellCommand', docker)
allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(false)
allow(File).to receive(:open)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log", "w")
Runtime::Scenario.define(:omnibus_configuration, Runtime::OmnibusConfiguration.new)
end
it 'configures omnibus by writing gitlab.rb' do
gitlab_component.reconfigure
cfg = Runtime::Scenario.omnibus_configuration.to_s.gsub('"', '\\"')
expect(docker).to have_received(:new).with(
eq("docker exec #{gitlab_component.name} bash -c \"echo \\\"#{cfg}\\\" > /etc/gitlab/gitlab.rb;\""),
anything
)
end
end
context 'with secrets to mask' do
before do
stub_const('Gitlab::QA::Docker::Engine', docker)
allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(false)
allow(File).to receive(:open)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log", "w")
gitlab_component.secrets = ['secret']
end
it 'passes secrets to docker engine' do
gitlab_component.reconfigure
expect(docker).to have_received(:write_files).with(gitlab_component.name, { mask_secrets: ['secret'] })
end
end
context 'without secrets to mask' do
before do
stub_const('Gitlab::QA::Docker::Engine', docker)
allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(false)
allow(File).to receive(:open)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log", "w")
end
it 'does not pass any secrets to docker engine' do
gitlab_component.reconfigure
expect(docker).to have_received(:write_files).with(gitlab_component.name, { mask_secrets: [] })
end
end
describe 'log file' do
let(:docker) { spy('docker') }
before do
stub_const('Gitlab::QA::Docker::Engine', docker)
allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir)
end
context 'when no log file is present' do
before do
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(false)
end
it 'writes to the log file' do
expect(File).to receive(:open).with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log", "w")
gitlab_component.reconfigure
end
end
context 'with retries' do
shared_examples 'creates a retry log file' do
it 'creates a retry log file' do
expect(File).to receive(:open).with(log_file, "w")
gitlab_component.reconfigure
end
end
context 'with the first retry' do
let(:log_file) { "#{artifacts_dir}/#{gitlab_component.name}-retry-1-reconfigure.log" }
before do
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(true)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-retry-1-reconfigure.log").and_return(false)
end
it_behaves_like 'creates a retry log file'
end
context 'with the second retry' do
let(:log_file) { "#{artifacts_dir}/#{gitlab_component.name}-retry-2-reconfigure.log" }
before do
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-reconfigure.log").and_return(true)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-retry-1-reconfigure.log").and_return(true)
allow(File).to receive(:exist?)
.with("#{artifacts_dir}/#{gitlab_component.name}-retry-2-reconfigure.log").and_return(false)
end
it_behaves_like 'creates a retry log file'
end
end
end
end
describe '#create_key_file' do
let(:docker) { spy('docker') }
around do |example|
ClimateControl.modify(MY_KEY: 'key') { example.run }
end
it 'copies a key file' do
file_path = gitlab_component.create_key_file('MY_KEY')
stub_const('Gitlab::QA::Docker::Command', docker)
allow(gitlab_component).to receive(:ensure_configured!)
gitlab_component.start
expect(docker).to have_received(:volume)
.with(file_path, file_path, 'Z')
end
end
describe '#package_version' do
it 'returns locked version' do
allow(gitlab_component).to receive(:read_package_manifest)
.and_return('{"software":{"package-scripts":{"locked_version":"15.1.2"}}}')
expect(gitlab_component.package_version).to eq('15.1.2')
end
end
describe '#exist' do
let(:docker) { spy('docker') }
it 'calls and returns docker exist true' do
stub_const('Gitlab::QA::Docker::Engine', docker)
expect(docker).to receive(:manifest_exists?).with('foo/bar:xyz').and_return(true)
expect(gitlab_component.exist?('foo/bar', 'xyz')).to be(true)
end
it 'calls and returns docker exist false' do
stub_const('Gitlab::QA::Docker::Engine', docker)
expect(docker).to receive(:manifest_exists?).with('bar/foo:xyz').and_return(false)
expect(gitlab_component.exist?('bar/foo', 'xyz')).to be(false)
end
end
describe '#process_exec_commands' do
let(:docker) { spy('docker') }
let(:secret) { 'user-agent-secret' }
let(:command) { "command with secret: #{secret}" }
before do
stub_const('Gitlab::QA::Docker::Engine', docker)
allow(Runtime::Scenario).to receive_messages(omnibus_exec_commands: [command], seed_admin_token: false)
end
context 'with secrets to mask' do
it 'passes secrets to docker engine' do
gitlab_component.secrets = [secret]
gitlab_component.process_exec_commands
expect(docker).to have_received(:exec).with(gitlab_component.name, command, { mask_secrets: [secret] })
end
end
context 'without secrets to mask' do
it 'does not pass any secrets to docker engine' do
gitlab_component.process_exec_commands
expect(docker).to have_received(:exec).with(gitlab_component.name, command, { mask_secrets: [] })
end
end
end
describe '#set_qa_user_agent' do
around do |example|
ClimateControl.modify(GITLAB_QA_USER_AGENT: 'user-agent-secret') { example.run }
end
it 'sets GITLAB_QA_USER_AGENT as a rails env var' do
gitlab_component.set_qa_user_agent
expect(gitlab_component.omnibus_gitlab_rails_env)
.to include({ 'GITLAB_QA_USER_AGENT' => 'user-agent-secret' })
end
it 'treats the value of GITLAB_QA_USER_AGENT as a secret' do
gitlab_component.set_qa_user_agent
expect(gitlab_component.secrets).to include('user-agent-secret')
end
end
private
def create_release(release)
Release.new(release)
end
end
end
end