spec/chef/cookbooks/gitlab/recipes/gitlab-rails_spec.rb (1,688 lines of code) (raw):

require 'chef_helper' RSpec.describe 'gitlab::gitlab-rails' do using RSpec::Parameterized::TableSyntax let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink runit_service)).converge('gitlab::default') } let(:redis_instances) { RedisHelper::GitlabRails::REDIS_INSTANCES } let(:redis_cluster_instances) { %w(cache rate_limiting cluster_rate_limiting) } let(:config_dir) { '/var/opt/gitlab/gitlab-rails/etc/' } let(:default_vars) do { 'HOME' => '/var/opt/gitlab', 'RAILS_ENV' => 'production', 'SIDEKIQ_MEMORY_KILLER_MAX_RSS' => '2000000', 'BUNDLE_GEMFILE' => '/opt/gitlab/embedded/service/gitlab-rails/Gemfile', 'PATH' => '/opt/gitlab/bin:/opt/gitlab/embedded/bin:/bin:/usr/bin', 'ICU_DATA' => '/opt/gitlab/embedded/share/icu/current', 'PYTHONPATH' => '/opt/gitlab/embedded/lib/python3.9/site-packages', 'EXECJS_RUNTIME' => 'Disabled', 'TZ' => ':/etc/localtime', 'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/', 'SSL_CERT_FILE' => '/opt/gitlab/embedded/ssl/cert.pem', 'PUMA_WORKER_MAX_MEMORY' => nil } end before do allow(Gitlab).to receive(:[]).and_call_original allow(File).to receive(:symlink?).and_call_original end context 'with defaults' do it 'creates a default VERSION file and restarts services' do expect(chef_run).to create_version_file('Create version file for Rails').with( version_file_path: '/var/opt/gitlab/gitlab-rails/RUBY_VERSION', version_check_cmd: '/opt/gitlab/embedded/bin/ruby --version' ) dependent_services = [] dependent_services.each do |svc| expect(chef_run.version_file('Create version file for Rails')).to notify("runit_service[#{svc}]").to(:restart) end end end context 'when manage-storage-directories is disabled' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared', uploads_directory: '/tmp/uploads', uploads_storage_path: '/tmp/uploads_storage' }, gitlab_ci: { builds_directory: '/tmp/builds' }, git_data_dirs: { "some_storage" => { "path" => "/tmp/git-data" } }, manage_storage_directories: { enable: false }) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'does not create the git-data directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data') end it 'does not create the repositories directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/git-data/repositories') end it 'does not create the shared directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared') end it 'does not create the artifacts directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/artifacts') end it 'does not create the external-diffs directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/external-diffs') end it 'does not create the lfs storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/lfs-objects') end it 'does not create the packages storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/packages') end it 'does not create the dependency_proxy storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/dependency_proxy') end it 'does not create the terraform_state storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/terraform_state') end it 'does not create the ci_secure_files storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/ci_secure_files') end it 'does not create the GitLab pages directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/shared/pages') end it 'does not create the uploads directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads') end it 'does not create the ci builds directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/builds') end it 'does not create the uploads storage directory' do expect(chef_run).not_to run_ruby_block('directory resource: /tmp/uploads_storage') end end context 'when manage-storage-directories is enabled' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb(gitlab_rails: { shared_path: '/tmp/shared', uploads_directory: '/tmp/uploads', uploads_storage_path: '/tmp/uploads_storage' }, gitlab_ci: { builds_directory: '/tmp/builds' }, gitaly: { configuration: { storage: [ { 'name' => 'some_storage', 'path' => '/tmp/git-data/repositories' } ] } }) end ChefSpec::SoloRunner.converge('gitlab::default') end include_examples "git data directory", "/tmp/git-data/repositories" it 'creates the shared directory' do expect(chef_run).to create_storage_directory('/tmp/shared').with(owner: 'git', group: 'gitlab-www', mode: '0751') end it 'creates the artifacts directory' do expect(chef_run).to create_storage_directory('/tmp/shared/artifacts').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the external-diffs directory' do expect(chef_run).to create_storage_directory('/tmp/shared/external-diffs').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the lfs storage directory' do expect(chef_run).to create_storage_directory('/tmp/shared/lfs-objects').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the packages directory' do expect(chef_run).to create_storage_directory('/tmp/shared/packages').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the dependency_proxy directory' do expect(chef_run).to create_storage_directory('/tmp/shared/dependency_proxy').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the terraform_state directory' do expect(chef_run).to create_storage_directory('/tmp/shared/terraform_state').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the ci_secure_files directory' do expect(chef_run).to create_storage_directory('/tmp/shared/ci_secure_files').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the encrypted_settings directory' do expect(chef_run).to create_storage_directory('/tmp/shared/encrypted_settings').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the GitLab pages directory' do expect(chef_run).to create_storage_directory('/tmp/shared/pages').with(owner: 'git', group: 'gitlab-www', mode: '0750') end it 'creates the shared tmp directory' do expect(chef_run).to create_storage_directory('/tmp/shared/tmp').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the shared cache directory' do expect(chef_run).to create_storage_directory('/tmp/shared/cache').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the uploads directory' do expect(chef_run).to create_storage_directory('/tmp/uploads').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the ci builds directory' do expect(chef_run).to create_storage_directory('/tmp/builds').with(owner: 'git', group: 'git', mode: '0700') end it 'creates the uploads storage directory' do expect(chef_run).to create_storage_directory('/tmp/uploads_storage').with(owner: 'git', group: 'git', mode: '0700') end end context 'when uploads storage directory is not specified' do cached(:chef_run) do ChefSpec::SoloRunner.converge('gitlab::default') end it 'does not create the uploads storage directory' do expect(chef_run).not_to create_storage_directory('/opt/gitlab/embedded/service/gitlab-rails/public') end end context 'with redis settings' do let(:config_file) { '/var/opt/gitlab/gitlab-rails/etc/resque.yml' } let(:resque_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/resque.yml') } let(:resque_yml_file_content) { ChefSpec::Renderer.new(chef_run, resque_yml_template).content } let(:resque_yml) { YAML.safe_load(resque_yml_file_content, aliases: true, symbolize_names: true) } let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') } context 'and default configuration' do it 'creates the config file with the required redis settings' do expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables( hash_including( redis_url: URI('unix:///var/opt/gitlab/redis/redis.socket'), redis_sentinels: [], redis_enable_client: true ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(url: unix:///var/opt/gitlab/redis/redis.socket$)) expect(content).not_to match(/id:/) expect(content).not_to match(/connect_timeout:/) expect(content).not_to match(/read_timeout:/) expect(content).not_to match(/write_timeout:/) } end it 'creates cable.yml with the same settings' do expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables( hash_including( redis_url: URI('unix:///var/opt/gitlab/redis/redis.socket'), redis_sentinels: [], redis_enable_client: true ) ) expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/cable.yml').with_content { |content| expect(content).to match(%r(url: unix:///var/opt/gitlab/redis/redis.socket$)) expect(content).not_to match(/connect_timeout:/) expect(content).not_to match(/read_timeout:/) expect(content).not_to match(/write_timeout:/) } end it 'does not render the separate instance configurations' do redis_instances.each do |instance| expect(chef_run).not_to render_file("#{config_dir}redis.#{instance}.yml") end end it 'deletes the separate instance config files' do redis_instances.each do |instance| expect(chef_run).to delete_link("/opt/gitlab/embedded/service/gitlab-rails/config/redis.#{instance}.yml") expect(chef_run).to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml") end end end context 'and custom configuration' do before do stub_gitlab_rb( gitlab_rails: { redis_host: 'redis.example.com', redis_port: 8888, redis_database: 2, redis_password: 'my pass', redis_enable_client: false, redis_connect_timeout: 3, redis_read_timeout: 4, redis_write_timeout: 5 } ) end it 'creates the config file with custom host, port, password, database, and timeouts' do expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root').with_variables( hash_including( redis_url: URI('redis://:my%20pass@redis.example.com:8888/2'), redis_sentinels: [], redis_enable_client: false, redis_connect_timeout: 3, redis_read_timeout: 4, redis_write_timeout: 5 ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(url: redis://:my%20pass@redis.example.com:8888/2)) expect(content).to match(/id:$/) expect(content).to match(/connect_timeout: 3/) expect(content).to match(/read_timeout: 4/) expect(content).to match(/write_timeout: 5/) } end it 'creates cable.yml with custom host, port, password and database' do expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables( hash_including( redis_url: URI('redis://:my%20pass@redis.example.com:8888/2'), redis_sentinels: [], redis_enable_client: false ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(url: redis://:my%20pass@redis.example.com:8888/2)) expect(content).to match(/id:$/) } end context 'with Redis sentinels configured' do let(:expected_output) do { production: { url: 'redis://:toomanysecrets@gitlab-redis/', sentinels: [ { host: '10.0.0.2', port: 26379 }, { host: '10.0.0.3', port: 26379 }, { host: '10.0.0.4', port: 26379 }, ], secret_file: '/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.yml.enc', } } end context 'with Redis master details specified through redis subkey' do before do stub_gitlab_rb( redis: { enable: false, master_name: 'gitlab-redis', master_password: 'toomanysecrets' }, gitlab_rails: { redis_sentinels: [ { host: '10.0.0.2', port: 26379 }, { host: '10.0.0.3', port: 26379 }, { host: '10.0.0.4', port: 26379 }, ] } ) end it 'populates resque.yml with expected values' do expect(resque_yml).to eq(expected_output) end end context 'with Redis master details specified through gitlab_rails subkey' do before do stub_gitlab_rb( redis: { enable: false, }, gitlab_rails: { redis_sentinel_master: 'gitlab-redis', redis_password: 'toomanysecrets', redis_sentinels: [ { host: '10.0.0.2', port: 26379 }, { host: '10.0.0.3', port: 26379 }, { host: '10.0.0.4', port: 26379 }, ] } ) end it 'populates resque.yml with expected values' do expect(resque_yml).to eq(expected_output) end end context 'with Redis Sentinels password configured' do let(:cable_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/cable.yml') } let(:cable_yml_file_content) { ChefSpec::Renderer.new(chef_run, cable_yml_template).content } let(:cable_yml) { YAML.safe_load(cable_yml_file_content, aliases: true, symbolize_names: true) } let(:sentinel_password) { "global sentinel pass" } let(:expected_output) do { production: { url: 'redis://:toomanysecrets@gitlab-redis/', sentinels: [ { host: '10.0.0.2', port: 26379, password: sentinel_password }, { host: '10.0.0.3', port: 26379, password: sentinel_password }, { host: '10.0.0.4', port: 26379, password: sentinel_password }, ], secret_file: '/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.yml.enc', } } end let(:expected_cable_output) do { production: { adapter: 'redis', url: 'redis://:toomanysecrets@gitlab-redis/', sentinels: [ { host: '10.0.0.2', port: 26379, password: sentinel_password }, { host: '10.0.0.3', port: 26379, password: sentinel_password }, { host: '10.0.0.4', port: 26379, password: sentinel_password }, ] } } end before do stub_gitlab_rb( redis: { enable: true, master_name: 'gitlab-redis', master_password: 'toomanysecrets' }, gitlab_rails: { redis_sentinels_password: 'global sentinel pass', redis_sentinels: [ { host: '10.0.0.2', port: 26379 }, { host: '10.0.0.3', port: 26379 }, { host: '10.0.0.4', port: 26379 }, ] } ) end it 'populates resque.yml with expected values' do expect(resque_yml).to eq(expected_output) expect(cable_yml).to eq(expected_cable_output) end context 'with ActionCable Redis Sentinels defined' do let(:expected_output) do { production: { url: 'redis://:toomanysecrets@gitlab-redis/', sentinels: [ { host: '10.0.0.2', port: 26379, password: sentinel_password }, { host: '10.0.0.3', port: 26379, password: sentinel_password }, { host: '10.0.0.4', port: 26379, password: sentinel_password }, ], secret_file: '/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.yml.enc', } } end let(:expected_cable_output) do { production: { adapter: 'redis', url: 'redis://:fakepass@fake.redis.actioncable.com:8888/2', sentinels: [ { host: '10.0.0.5', port: 26379 }, { host: '10.0.0.6', port: 26379 }, { host: '10.0.0.7', port: 26379 }, ] } } end before do stub_gitlab_rb( redis: { enable: true, master_name: 'gitlab-redis', master_password: 'toomanysecrets' }, gitlab_rails: { redis_sentinels_password: 'global sentinel pass', redis_sentinels: [ { host: '10.0.0.2', port: 26379 }, { host: '10.0.0.3', port: 26379 }, { host: '10.0.0.4', port: 26379 }, ], redis_actioncable_instance: "redis://:fakepass@fake.redis.actioncable.com:8888/2", redis_actioncable_sentinels: [ { host: '10.0.0.5', port: 26379 }, { host: '10.0.0.6', port: 26379 }, { host: '10.0.0.7', port: 26379 }, ] } ) end it 'populates resque.yml with expected values' do expect(resque_yml).to eq(expected_output) expect(cable_yml).to eq(expected_cable_output) end end end end end context 'with TLS settings' do before do stub_gitlab_rb( gitlab_rails: { redis_host: 'redis.example.com', redis_port: 8888, redis_password: 'mypass', redis_ssl: true, redis_tls_ca_cert_dir: '/tmp/certs', redis_tls_ca_cert_file: '/tmp/ca.crt', redis_tls_client_cert_file: '/tmp/self_signed.crt', redis_tls_client_key_file: '/tmp/self_signed.key', } ) end it 'renders configuration with tls settings' do expected_output = { ca_file: "/tmp/ca.crt", ca_path: "/tmp/certs", cert_file: "/tmp/self_signed.crt", key_file: "/tmp/self_signed.key" } expect(resque_yml[:production][:ssl_params]).to eq(expected_output) end end shared_examples 'instances does not support redis cluster' do |instance| context "with disallowed instance: #{instance}" do before do stub_gitlab_rb( gitlab_rails: { "redis_#{instance}_cluster_nodes" => [ { 'host' => 'cluster1.example.com', 'port' => '12345' } ] } ) end it 'defining cluster_nodes raises error' do expect { chef_run }.to raise_error(RuntimeError) end end end context 'with multiple redis cluster instance' do %w(queues shared_state trace_chunks sessions).each do |instance| it_should_behave_like 'instances does not support redis cluster', instance end context 'with allowed instances' do before do stub_gitlab_rb( gitlab_rails: RedisHelper::GitlabRails::ALLOWED_REDIS_CLUSTER_INSTANCE.to_h do |inst| ["redis_#{inst}_cluster_nodes", { 'host' => 'cluster1.example.com', 'port' => '12345' }] end ) end it 'does not raise error' do expect { chef_run }.not_to raise_error(RuntimeError) end end end context 'with a password and UNIX socket' do let(:cable_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/cable.yml') } let(:cable_yml_file_content) { ChefSpec::Renderer.new(chef_run, cable_yml_template).content } let(:cable_yml) { YAML.safe_load(cable_yml_file_content, aliases: true, symbolize_names: true) } let(:encoded_password) { "my%20pass%40" } before do stub_gitlab_rb( gitlab_rails: { redis_password: 'my pass@', } ) end it 'renders resque.yml with password' do expected_output = { url: "unix://:#{encoded_password}@/var/opt/gitlab/redis/redis.socket", secret_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.yml.enc" } expect(resque_yml[:production]).to eq(expected_output) end it 'creates cable.yml with password' do expected_output = { adapter: 'redis', url: "unix://:#{encoded_password}@/var/opt/gitlab/redis/redis.socket", } expect(cable_yml[:production]).to eq(expected_output) end end context 'with multiple instances' do context 'with action cable' do before do stub_gitlab_rb(gitlab_rails: { redis_enable_client: false, redis_actioncable_instance: "redis://:fakepass@fake.redis.actioncable.com:8888/2", redis_actioncable_sentinels: [{ host: 'actioncable', port: '1234' }, { host: 'actioncable', port: '3456' }], redis_actioncable_sentinels_password: 'some pass' }) end it 'still renders the default configuration file' do expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root') end it 'creates cable.yml with custom settings' do expect(chef_run).to create_templatesymlink('Create a cable.yml and create a symlink to Rails root').with_variables( hash_including( redis_url: "redis://:fakepass@fake.redis.actioncable.com:8888/2", redis_sentinels: [{ 'host' => 'actioncable', 'port' => '1234' }, { 'host' => 'actioncable', 'port' => '3456' }], redis_sentinels_password: 'some pass', redis_enable_client: false ) ) expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/cable.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'url')).to eq("redis://:fakepass@fake.redis.actioncable.com:8888/2") expect(generated_yml.dig('production', 'cluster')).to eq(nil) expect(generated_yml.dig('production', 'sentinels')).to eq([{ "host" => 'actioncable', 'port' => 1234, 'password' => 'some pass' }, { 'host' => 'actioncable', 'port' => 3456, 'password' => 'some pass' }]) } end end context 'with cluster configs' do before do stub_hash = { redis_enable_client: false } redis_cluster_instances.each do |instance| stub_hash["redis_#{instance}_username"] = instance stub_hash["redis_#{instance}_password"] = "#{instance}_password" stub_hash["redis_#{instance}_cluster_nodes"] = [{ host: instance, port: '1234' }, { host: instance, port: '3456' }] end stub_gitlab_rb(gitlab_rails: stub_hash) end it 'still renders the default configuration file' do expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root') end it 'render separate cluster config files' do redis_cluster_instances.each do |instance| expect(chef_run).to create_templatesymlink("Create a redis.#{instance}.yml and create a symlink to Rails root").with_variables( redis_url: nil, redis_sentinels: [], redis_sentinels_password: nil, redis_enable_client: false, cluster_nodes: [{ "host" => instance, "port" => "1234" }, { "host" => instance, "port" => "3456" }], cluster_username: instance, cluster_password: "#{instance}_password", redis_ssl: false, redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/", redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem", redis_tls_client_cert_file: nil, redis_tls_client_key_file: nil, redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc", redis_extra_config_command: nil ) expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'url')).to eq(nil) expect(generated_yml.dig('production', 'sentinels')).to eq(nil) expect(generated_yml.dig('production', 'cluster')).to eq([{ "host" => instance, "port" => 1234 }, { "host" => instance, "port" => 3456 }]) expect(generated_yml.dig('production', 'username')).to eq(instance) expect(generated_yml.dig('production', 'password')).to eq("#{instance}_password") } expect(chef_run).not_to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml") end end end context 'with sentinel configs' do before do stub_hash = { redis_enable_client: false } redis_instances.each do |instance| stub_hash["redis_#{instance}_instance"] = "redis://:fakepass@fake.redis.#{instance}.com:8888/2" stub_hash["redis_#{instance}_sentinels"] = [{ host: instance, port: '1234' }, { host: instance, port: '3456' }] end stub_gitlab_rb(gitlab_rails: stub_hash) end it 'render separate config files' do redis_instances.each do |instance| expect(chef_run).to create_templatesymlink("Create a redis.#{instance}.yml and create a symlink to Rails root").with_variables( redis_url: "redis://:fakepass@fake.redis.#{instance}.com:8888/2", redis_sentinels: [{ "host" => instance, "port" => "1234" }, { "host" => instance, "port" => "3456" }], redis_sentinels_password: nil, redis_enable_client: false, cluster_nodes: [], cluster_username: nil, cluster_password: nil, redis_ssl: false, redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/", redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem", redis_tls_client_cert_file: nil, redis_tls_client_key_file: nil, redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc", redis_extra_config_command: nil ) expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'url')).to eq("redis://:fakepass@fake.redis.#{instance}.com:8888/2") expect(generated_yml.dig('production', 'sentinels')).to eq([{ "host" => instance, "port" => 1234 }, { "host" => instance, "port" => 3456 }]) expect(generated_yml.dig('production', 'cluster')).to eq(nil) expect(generated_yml.dig('production', 'username')).to eq(nil) expect(generated_yml.dig('production', 'password')).to eq(nil) } expect(chef_run).not_to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml") end end context 'with Sentinel passwords' do before do stub_hash = { redis_enable_client: false } redis_instances.each do |instance| stub_hash["redis_#{instance}_instance"] = "redis://:fakepass@fake.redis.#{instance}.com:8888/2" stub_hash["redis_#{instance}_sentinels"] = [{ host: instance, port: '1234' }, { host: instance, port: '3456' }] stub_hash["redis_#{instance}_sentinels_password"] = 'sentinelpass' end stub_gitlab_rb(gitlab_rails: stub_hash) end it 'render separate config files' do redis_instances.each do |instance| expect(chef_run).to create_templatesymlink("Create a redis.#{instance}.yml and create a symlink to Rails root").with_variables( redis_url: "redis://:fakepass@fake.redis.#{instance}.com:8888/2", redis_sentinels: [{ "host" => instance, "port" => "1234" }, { "host" => instance, "port" => "3456" }], redis_sentinels_password: 'sentinelpass', redis_enable_client: false, cluster_nodes: [], cluster_username: nil, cluster_password: nil, redis_ssl: false, redis_tls_ca_cert_dir: "/opt/gitlab/embedded/ssl/certs/", redis_tls_ca_cert_file: "/opt/gitlab/embedded/ssl/certs/cacert.pem", redis_tls_client_cert_file: nil, redis_tls_client_key_file: nil, redis_encrypted_settings_file: "/var/opt/gitlab/gitlab-rails/shared/encrypted_settings/redis.#{instance}.yml.enc", redis_extra_config_command: nil ) expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'url')).to eq("redis://:fakepass@fake.redis.#{instance}.com:8888/2") expect(generated_yml.dig('production', 'sentinels')).to eq([{ "host" => instance, "port" => 1234, "password" => 'sentinelpass' }, { "host" => instance, "port" => 3456, "password" => 'sentinelpass' }]) expect(generated_yml.dig('production', 'cluster')).to eq(nil) expect(generated_yml.dig('production', 'username')).to eq(nil) expect(generated_yml.dig('production', 'password')).to eq(nil) } expect(chef_run).not_to delete_file("/var/opt/gitlab/gitlab-rails/etc/redis.#{instance}.yml") end end end it 'still renders the default configuration file' do expect(chef_run).to create_templatesymlink('Create a resque.yml and create a symlink to Rails root') end end describe 'encrypted_settings_file' do cached(:chef_run) do ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') end let(:cache_secret_file) { '/etc/gitlab/cache.redis.enc' } let(:global_secret_file) { '/etc/gitlab/global.redis.enc' } context 'with separate file for an instance' do before do stub_gitlab_rb( gitlab_rails: { redis_encrypted_settings_file: global_secret_file, redis_cache_instance: 'redis://redis.cache.instance', redis_cache_encrypted_settings_file: cache_secret_file, redis_shared_state_instance: 'redis://redis.shared_state.instance' } ) end it 'uses specified path for the cache instance' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.cache.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'secret_file')).to eq(cache_secret_file) } end it 'uses global path for the shared state instance' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.shared_state.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'secret_file')).to eq(global_secret_file) } end end end describe 'extra config command' do let(:cache_command) { '/opt/redis-cache-password.sh' } let(:global_command) { '/opt/redis-global.sh' } context 'with single Redis instance' do before do stub_gitlab_rb( gitlab_rails: { redis_extra_config_command: global_command } ) end it 'populates resque.yml with specified config command' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/resque.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'config_command')).to eq(global_command) } end it 'populates cable.yml with specified config command' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/cable.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'config_command')).to eq(global_command) } end end context 'with separate command for an instance' do cached(:chef_run) do ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') end before do stub_gitlab_rb( gitlab_rails: { redis_extra_config_command: global_command, redis_cache_instance: 'redis://redis.cache.instance', redis_cache_extra_config_command: cache_command, redis_shared_state_instance: 'redis://redis.shared_state.instance' } ) end it 'uses specified command for the cache instance' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.cache.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'config_command')).to eq(cache_command) } end it 'uses global command for the shared state instance' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/redis.shared_state.yml").with_content { |content| generated_yml = YAML.safe_load(content) expect(generated_yml.dig('production', 'config_command')).to eq(global_command) } end end end end context 'redis.yml override' do let(:redis_yml_file_path) { '/var/opt/gitlab/gitlab-rails/etc/redis.yml' } let(:redis_yml_link_path) { '/opt/gitlab/embedded/service/gitlab-rails/config/redis.yml' } it 'renders empty redis.yml' do expect(chef_run).to create_link(redis_yml_link_path).with(to: redis_yml_file_path) expect(chef_run).to render_file(redis_yml_file_path).with_content { |content| expect(content.strip).to eq('') } end context 'when configured' do let(:redis_yml_hash) do { 'foo' => 'bar', 'baz' => [1, 2, 3], 'deeply' => { 'nested' => 'value' } } end before do stub_gitlab_rb( gitlab_rails: { redis_yml_override: redis_yml_hash } ) end it 'renders redis.yml' do expect(chef_run).to create_link(redis_yml_link_path).with(to: redis_yml_file_path) expect(chef_run).to render_file(redis_yml_file_path).with_content { |content| actual = YAML.safe_load(content) expect(actual).to eq({ 'production' => redis_yml_hash }) } end end end end describe 'gitlab.yml' do gitlab_yml_path = '/var/opt/gitlab/gitlab-rails/etc/gitlab.yml' let(:gitlab_yml) { chef_run.template(gitlab_yml_path) } let(:gitlab_yml_templatesymlink) { chef_run.templatesymlink('Create a gitlab.yml and create a symlink to Rails root') } let(:gitlab_yml_file_content) { ChefSpec::Renderer.new(chef_run, gitlab_yml).content } let(:parsed_gitlab_yml) { YAML.safe_load(gitlab_yml_file_content, aliases: true, symbolize_names: true) } # NOTE: Test if we pass proper notifications to other resources describe 'rails cache management' do before do stub_default_not_listening?(false) end context 'with default values' do it 'should notify rails cache clear resource' do expect(gitlab_yml_templatesymlink).to notify('execute[clear the gitlab-rails cache]') end end context 'with rake_cache_clear set to false' do before do stub_gitlab_rb(gitlab_rails: { rake_cache_clear: false }) end it 'should notify rails cache clear resource' do expect(gitlab_yml_templatesymlink).to notify( 'execute[clear the gitlab-rails cache]') end it 'should not run cache clear' do expect(chef_run).not_to run_execute( 'clear the gitlab-rails cache') end end context 'SSH settings' do context 'defaults' do it 'omits the SSH host and port' do expect(parsed_gitlab_yml[:production][:gitlab][:ssh_host]).to be_nil expect(parsed_gitlab_yml[:production][:gitlab_shell][:ssh_port]).to be_nil end end context 'with a custom SSH hostname and port' do before do stub_gitlab_rb( gitlab_rails: { gitlab_ssh_host: 'gitlab.example.com', gitlab_shell_ssh_port: 2222 } ) end it 'renders the SSH host and port' do expect(parsed_gitlab_yml[:production][:gitlab][:ssh_host]).to eq('gitlab.example.com') expect(parsed_gitlab_yml[:production][:gitlab_shell][:ssh_port]).to eq(2222) end end context 'with an invalid SSH hostname' do before do stub_gitlab_rb( gitlab_rails: { gitlab_ssh_host: 'gitlab.example.com:2222' } ) end it 'raises an exception' do expect { chef_run }.to raise_error(RuntimeError, /If you wish to use a custom SSH port/) end end context 'with custom html header tags' do before do stub_gitlab_rb( gitlab_rails: { custom_html_header_tags: '<script src="https://example.com/cookie-consent.js"></script><link rel="stylesheet" href="https://example.com/cookie-consent.css"/>' } ) end it 'renders the custom_html_header_tags' do expect(parsed_gitlab_yml[:production][:gitlab][:custom_html_header_tags]).to eq('<script src="https://example.com/cookie-consent.js"></script><link rel="stylesheet" href="https://example.com/cookie-consent.css"/>') end end context 'without custom html header tags' do it 'does not render the custom_html_header_tags' do expect(parsed_gitlab_yml[:production][:gitlab][:custom_html_header_tags]).to be_nil end end end end end context 'with environment variables' do context 'by default' do it 'creates necessary env variable files' do expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables(default_vars) end context 'when a custom env variable is specified' do before do stub_gitlab_rb(gitlab_rails: { env: { 'IAM' => 'CUSTOMVAR' } }) end it 'creates necessary env variable files' do expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables( default_vars.merge( { 'IAM' => 'CUSTOMVAR' } ) ) end end end context 'when puma per_worker_max_memory_mb is configured' do before do stub_gitlab_rb(puma: { per_worker_max_memory_mb: 1200 }) end it 'creates necessary env variable files' do expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables( default_vars.merge( { 'PUMA_WORKER_MAX_MEMORY' => 1200 } ) ) end end context 'when relative URL is enabled' do before do stub_gitlab_rb(gitlab_rails: { gitlab_relative_url: '/gitlab' }) end it 'creates necessary env variable files' do expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables( default_vars.merge( { 'RAILS_RELATIVE_URL_ROOT' => '/gitlab' } ) ) end end context 'when relative URL is specified in external_url' do before do stub_gitlab_rb(external_url: 'http://localhost/gitlab') end it 'creates necessary env variable files' do expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitlab-rails/env').with_variables( default_vars.merge( { 'RAILS_RELATIVE_URL_ROOT' => '/gitlab' } ) ) end end end describe "with symlinked templates" do let(:chef_run) { ChefSpec::SoloRunner.new.converge('gitlab::default') } before do %w( alertmanager gitlab-exporter gitlab-pages gitlab-kas gitlab-workhorse logrotate nginx node-exporter postgres-exporter postgresql prometheus redis redis-exporter sidekiq puma gitaly ).map { |svc| stub_should_notify?(svc, true) } end describe 'database.yml' do database_yml_path = '/var/opt/gitlab/gitlab-rails/etc/database.yml' let(:database_yml) { chef_run.template(database_yml_path) } let(:database_yml_content) { ChefSpec::Renderer.new(chef_run, database_yml).content } let(:generated_yml_content) { YAML.safe_load(database_yml_content) } let(:config_file) { database_yml_path } let(:templatesymlink) { chef_run.templatesymlink('Create a database.yml and create a symlink to Rails root') } context 'by default' do cached(:chef_run) do ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_host' => '/var/opt/gitlab/postgresql', 'db_database' => 'gitlabhq_production', 'db_load_balancing' => { 'hosts' => [] }, 'db_prepared_statements' => false, 'db_sslcompression' => 0, 'db_sslcert' => nil, 'db_sslkey' => nil, 'db_application_name' => nil, 'db_extra_config_command' => nil ) ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed end it 'renders expected YAML' do expect(generated_yml_content.dig('production', 'main', 'adapter')).to eq('postgresql') expect(generated_yml_content.dig('production', 'main', 'host')).to eq('/var/opt/gitlab/postgresql') expect(generated_yml_content.dig('production', 'main', 'port')).to eq(5432) expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(nil) end end context 'with specific database settings' do context 'with an application name set' do where(:appname, :expected) do '' | '' 'test' | 'test' end with_them do cached(:chef_run) do ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') end before do stub_gitlab_rb( 'gitlab_rails' => { 'db_application_name' => appname } ) end it 'renders expected YAML' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_application_name' => appname ) ) expect(generated_yml_content.dig('production', 'main').keys).to include(*%w(adapter host port application_name)) expect(generated_yml_content.dig('production', 'main', 'application_name')).to eq(expected) end end end context 'when multiple postgresql listen_address is used' do before do stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1,1.1.1.1" }) end it 'creates the postgres configuration file with multi listen_address and database.yml file with one host' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_host' => '127.0.0.1' ) ) end end context 'when no postgresql listen_address is used' do it 'creates the postgres configuration file with empty listen_address and database.yml file with default one' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_host' => '/var/opt/gitlab/postgresql' ) ) end end context 'when one postgresql listen_address is used' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb(postgresql: { listen_address: "127.0.0.1" }) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the postgres configuration file with one listen_address and database.yml file with one host' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_host' => '127.0.0.1' ) ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed expect(templatesymlink).not_to notify('runit_service[gitlab-workhorse]').to(:restart).delayed expect(templatesymlink).not_to notify('runit_service[nginx]').to(:restart).delayed end end context 'when load balancers are specified' do before do stub_gitlab_rb(gitlab_rails: { db_load_balancing: { 'hosts' => ['primary.example.com', 'secondary.example.com'] } }) end it 'uses provided value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_load_balancing' => { 'hosts' => ['primary.example.com', 'secondary.example.com'] } ) ) end end context 'when prepared_statements are disabled' do before do stub_gitlab_rb(gitlab_rails: { db_prepared_statements: false }) end it 'uses provided value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_prepared_statements' => false, 'db_statements_limit' => 1000 ) ) end end context 'when limit for prepared_statements are specified' do before do stub_gitlab_rb(gitlab_rails: { db_statements_limit: 12345 }) end it 'uses provided value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_prepared_statements' => false, 'db_statements_limit' => 12345 ) ) end end context 'when SSL compression is enabled' do before do stub_gitlab_rb(gitlab_rails: { db_sslcompression: 1 }) end it 'uses provided value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_sslcompression' => 1 ) ) end end context 'when SSL certificate and key for DB is specified' do before do stub_gitlab_rb( gitlab_rails: { db_sslcert: '/etc/certs/db.cer', db_sslkey: '/etc/certs/db.key' } ) end it 'uses specified value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_sslcert' => '/etc/certs/db.cer', 'db_sslkey' => '/etc/certs/db.key' ) ) end end context 'when db_extra_config_command is specified' do cached(:chef_run) do ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') end before do stub_gitlab_rb( gitlab_rails: { db_extra_config_command: '/opt/database-config.sh' } ) end it 'uses specified value in database.yml' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_extra_config_command' => '/opt/database-config.sh' ) ) expect(generated_yml_content.dig('production', 'config_command')).to eq('/opt/database-config.sh') end end end describe 'client side statement_timeout' do let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') } context 'default values' do it 'does not set a default value' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_statement_timeout' => nil ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(statement_timeout: $)) } end end context 'custom value' do before do stub_gitlab_rb( 'postgresql' => { 'statement_timeout' => '65000' }, 'gitlab_rails' => { 'db_statement_timeout' => '70000' } ) end it 'uses specified client side statement_timeout value' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( 'db_statement_timeout' => '70000' ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(statement_timeout: 70000$)) } end end end context 'adjusting database adapter connection parameters' do let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink)).converge('gitlab::default') } using RSpec::Parameterized::TableSyntax where(:rb_param, :yaml_param, :default_value, :custom_value) do 'db_connect_timeout' | 'connect_timeout' | nil | 5 'db_keepalives' | 'keepalives' | nil | 1 'db_keepalives_idle' | 'keepalives_idle' | nil | 5 'db_keepalives_interval' | 'keepalives_interval' | nil | 3 'db_keepalives_count' | 'keepalives_count' | nil | 3 'db_tcp_user_timeout' | 'tcp_user_timeout' | nil | 13000 end with_them do context 'default values' do it 'does not set a default value' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( rb_param => default_value ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(#{yaml_param}: $)) } end end context 'custom connection parameter value' do before do stub_gitlab_rb( 'gitlab_rails' => { rb_param => custom_value } ) end it 'uses specified connection parameter value' do expect(chef_run).to create_templatesymlink('Create a database.yml and create a symlink to Rails root').with_variables( hash_including( rb_param => custom_value ) ) expect(chef_run).to render_file(config_file).with_content { |content| expect(content).to match(%r(#{yaml_param}: #{custom_value}$)) } end end end end end describe 'gitlab_workhorse_secret' do let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root') } context 'by default' do cached(:chef_run) do ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end context 'with specific gitlab_workhorse_secret' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb(gitlab_workhorse: { secret_token: 'abc123-gitlab-workhorse' }) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'renders the correct node attribute' do expect(chef_run).to create_templatesymlink("Create a gitlab_workhorse_secret and create a symlink to Rails root").with_variables( secret_token: 'abc123-gitlab-workhorse' ) end it 'uses the correct owner and permissions' do expect(chef_run).to create_templatesymlink('Create a gitlab_workhorse_secret and create a symlink to Rails root').with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-workhorse]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end end describe 'gitlab_shell_secret' do let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_shell_secret and create a symlink to Rails root') } context 'by default' do cached(:chef_run) do ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitaly]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end context 'with gitlab-sshd enabled' do let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_shell_secret and create a symlink to Rails root') } cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_sshd: { enable: true } ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-sshd]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[gitaly]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end context 'with specific gitlab_shell_secret' do let(:gitlab_shell_secret_token) { SecureRandom.base64(32) } cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_shell: { secret_token: gitlab_shell_secret_token } ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'renders the correct node attribute' do expect(chef_run).to create_templatesymlink("Create a gitlab_shell_secret and create a symlink to Rails root").with_variables( secret_token: gitlab_shell_secret_token ) end it 'uses the correct owner and permissions' do expect(chef_run).to create_templatesymlink('Create a gitlab_shell_secret and create a symlink to Rails root').with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitaly]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end end describe 'gitlab_pages_secret' do let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root') } context 'with pages disabled' do let(:api_secret_key) { SecureRandom.base64(32) } cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( pages_enabled: false, gitlab_pages: { api_secret_key: api_secret_key, enable: false }, pages_external_url: 'http://pages.example.com' ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with( owner: 'root', group: 'root', mode: '0644' ) end end context 'by default' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( external_url: 'http://gitlab.example.com', pages_external_url: 'http://pages.example.com' ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'creates the template' do expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end context 'with specific gitlab_pages_secret' do let(:api_secret_key) { SecureRandom.base64(32) } cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_pages: { api_secret_key: api_secret_key }, external_url: 'http://gitlab.example.com', pages_external_url: 'http://pages.example.com' ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'renders the correct node attribute' do expect(chef_run).to create_templatesymlink("Create a gitlab_pages_secret and create a symlink to Rails root").with_variables( secret_token: api_secret_key ) end it 'uses the correct owner and permissions' do expect(chef_run).to create_templatesymlink('Create a gitlab_pages_secret and create a symlink to Rails root').with( owner: 'root', group: 'root', mode: '0644' ) end it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-pages]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end end describe 'gitlab_kas_secret' do let(:templatesymlink) { chef_run.templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root') } shared_examples 'creates the KAS template' do it 'creates the template' do expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with( owner: 'root', group: 'root', mode: '0644' ) end end context 'with KAS disabled' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_kas: { enable: false } ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it_behaves_like 'creates the KAS template' end context 'with KAS enabled' do cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_kas: { enable: true } ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it_behaves_like 'creates the KAS template' it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end context 'with specific gitlab_kas_secret' do let(:api_secret_key) { SecureRandom.base64(32) } cached(:chef_run) do RSpec::Mocks.with_temporary_scope do stub_gitlab_rb( gitlab_kas: { api_secret_key: api_secret_key } ) end ChefSpec::SoloRunner.new.converge('gitlab::default') end it 'renders the correct node attribute' do expect(chef_run).to create_templatesymlink('Create a gitlab_kas_secret and create a symlink to Rails root').with_variables( secret_token: api_secret_key ) end it_behaves_like 'creates the KAS template' it 'template triggers notifications' do expect(templatesymlink).to notify('runit_service[gitlab-kas]').to(:restart).delayed expect(templatesymlink).to notify('runit_service[puma]').to(:restart).delayed expect(templatesymlink).to notify('sidekiq_service[sidekiq]').to(:restart).delayed end end end end describe 'GitLab Registry files' do describe 'gitlab-registry.key file' do context 'Registry is disabled' do it 'does not generate gitlab-registry.key file' do expect(chef_run).not_to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key") end end context 'Registry is enabled' do context 'with default configuration' do before do stub_gitlab_rb( gitlab_rails: { registry_enabled: true } ) end it 'generates key file in the default location' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m) end end context 'with user specified configuration' do context 'when location of key file is specified' do before do stub_gitlab_rb( gitlab_rails: { registry_enabled: true, registry_key_path: '/fake/path' } ) end it 'generates key file in the specified location' do expect(chef_run).to render_file("/fake/path").with_content(/\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m) end end context 'when key content is specified' do before do stub_gitlab_rb( gitlab_rails: { registry_enabled: true }, registry: { internal_key: 'foobar' } ) end it 'generates key file with specified content' do expect(chef_run).to render_file("/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key").with_content('foobar') end end end end end end context 'SMTP settings' do context 'defaults' do before do stub_gitlab_rb( gitlab_rails: { smtp_enable: true } ) end it 'renders the default timeout values' do expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables( hash_including( 'smtp_open_timeout' => 30, 'smtp_read_timeout' => 60 ) ) expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content| expect(content).to include('open_timeout: 30') expect(content).to include('read_timeout: 60') } end end context 'when timeouts are set' do before do stub_gitlab_rb( gitlab_rails: { smtp_enable: true, smtp_open_timeout: 10, smtp_read_timeout: 20 } ) end it 'renders the timeout values' do expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables( hash_including( 'smtp_open_timeout' => 10, 'smtp_read_timeout' => 20 ) ) expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content| expect(content).to include('open_timeout: 10') expect(content).to include('read_timeout: 20') } end end context 'when connection pooling is not configured' do it 'creates smtp_settings.rb with pooling disabled' do stub_gitlab_rb( gitlab_rails: { smtp_enable: true } ) expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables( hash_including( 'smtp_pool' => false ) ) end end context 'when connection pooling is enabled' do it 'creates smtp_settings.rb with pooling enabled' do stub_gitlab_rb( gitlab_rails: { smtp_enable: true, smtp_pool: true } ) expect(chef_run).to create_templatesymlink('Create a smtp_settings.rb and create a symlink to Rails root').with_variables( hash_including( 'smtp_pool' => true ) ) expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content| expect(content).to include('ActionMailer::Base.delivery_method = :smtp_pool') } end end context 'when STARTTLS is enabled' do before do stub_gitlab_rb( gitlab_rails: { smtp_enable: true, smtp_enable_starttls_auto: true } ) end it 'enables STARTTLS in the settings' do expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content| expect(content).not_to include('tls =') expect(content).to include('enable_starttls_auto: true') } end end context 'when SMTP TLS is enabled' do before do stub_gitlab_rb( gitlab_rails: { smtp_enable: true, smtp_tls: true } ) end it 'enables SMTP TLS in the settings' do expect(chef_run).to render_file('/var/opt/gitlab/gitlab-rails/etc/smtp_settings.rb').with_content { |content| expect(content).to include('tls: true') expect(content).not_to include('enable_starttls_auto') } end end context 'when TLS and STARTTLS are enabled' do before do stub_gitlab_rb( gitlab_rails: { smtp_enable: true, smtp_tls: true, smtp_enable_starttls_auto: true } ) end it 'raises an exception' do expect { chef_run }.to raise_error(RuntimeError) end end end describe 'cleaning up the legacy sidekiq log symlink' do it 'removes the link if it existed' do allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { true } expect(chef_run).to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log') end it 'does nothing if it did not exist' do allow(File).to receive(:symlink?).with('/var/log/gitlab/gitlab-rails/sidekiq.log') { false } expect(chef_run).not_to delete_link('/var/log/gitlab/gitlab-rails/sidekiq.log') end end describe 'log directory and runit group' do context 'default values' do it_behaves_like 'enabled logged service', 'gitlab-rails', false, { log_directory_owner: 'git' } end context 'custom values' do before do stub_gitlab_rb( gitlab_rails: { log_group: 'fugee' } ) end it_behaves_like 'configured logrotate service', 'gitlab-rails', 'git', 'fugee' it_behaves_like 'enabled logged service', 'gitlab-rails', false, { log_directory_owner: 'git', log_group: 'fugee' } end end end