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