spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb (1,481 lines of code) (raw):
require 'chef_helper'
RSpec.describe 'gitaly' do
let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(runit_service)).converge('gitlab::default') }
let(:config_path) { '/var/opt/gitlab/gitaly/config.toml' }
let(:gitaly_config) { chef_run.template(config_path) }
let(:runtime_dir) { '/var/opt/gitlab/gitaly/user_defined/run' }
let(:socket_path) { '/tmp/gitaly.socket' }
let(:listen_addr) { 'localhost:7777' }
let(:tls_listen_addr) { 'localhost:8888' }
let(:certificate_path) { '/path/to/cert.pem' }
let(:key_path) { '/path/to/key.pem' }
let(:gpg_signing_key_path) { '/path/to/signing_key.gpg' }
let(:prometheus_listen_addr) { 'localhost:9000' }
let(:logging_level) { 'warn' }
let(:logging_format) { 'default' }
let(:logging_sentry_dsn) { 'https://my_key:my_secret@sentry.io/test_project' }
let(:logging_sentry_environment) { 'production' }
let(:prometheus_grpc_latency_buckets) do
[0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]
end
let(:auth_token) { '123#$secret456' }
let(:auth_transitioning) { true }
let(:graceful_restart_timeout) { '20m' }
let(:git_catfile_cache_size) { 50 }
let(:git_bin_path) { '/path/to/usr/bin/git' }
let(:use_bundled_git) { true }
let(:open_files_ulimit) { 10000 }
let(:default_vars) do
{
'SSL_CERT_DIR' => '/opt/gitlab/embedded/ssl/certs/',
'TZ' => ':/etc/localtime',
'HOME' => '/var/opt/gitlab',
'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',
'WRAPPER_JSON_LOGGING' => 'true',
"GITALY_PID_FILE" => '/var/opt/gitlab/gitaly/gitaly.pid',
}
end
let(:gitlab_url) { 'http://localhost:3000' }
let(:workhorse_addr) { 'localhost:4000' }
let(:gitaly_custom_hooks_dir) { '/path/to/gitaly/custom/hooks' }
let(:user) { 'user123' }
let(:password) { 'password321' }
let(:ca_file) { '/path/to/ca_file' }
let(:ca_path) { '/path/to/ca_path' }
let(:read_timeout) { 123 }
let(:daily_maintenance_start_hour) { 21 }
let(:daily_maintenance_start_minute) { 9 }
let(:daily_maintenance_duration) { '45m' }
let(:daily_maintenance_storages) { ["default"] }
let(:daily_maintenance_disabled) { false }
let(:cgroups_mountpoint) { '/sys/fs/cgroup' }
let(:cgroups_hierarchy_root) { 'gitaly' }
let(:cgroups_memory_bytes) { 2097152 }
let(:cgroups_cpu_shares) { 512 }
let(:cgroups_cpu_quota_us) { 400000 }
let(:cgroups_repositories_count) { 10 }
let(:cgroups_repositories_memory_bytes) { 1048576 }
let(:cgroups_repositories_cpu_shares) { 128 }
let(:cgroups_repositories_cpu_quota_us) { 200000 }
let(:cgroups_repositories_max_cgroups_per_repo) { 2 }
let(:pack_objects_cache_enabled) { true }
let(:pack_objects_cache_dir) { '/pack-objects-cache' }
let(:pack_objects_cache_max_age) { '10m' }
before do
allow(Gitlab).to receive(:[]).and_call_original
allow(SecretsHelper).to receive(:generate_hex).and_return('4ecd22c031fee5c7368a5a102f76dc41')
end
context 'by default' do
it_behaves_like "enabled runit service", "gitaly", "root", "root"
it 'creates expected directories with correct permissions' do
expect(chef_run).to create_directory('/var/opt/gitlab/gitaly').with(user: 'git', mode: '0700')
end
it 'creates a default VERSION file and restarts service' do
expect(chef_run).to create_version_file('Create version file for Gitaly').with(
version_file_path: '/var/opt/gitlab/gitaly/VERSION',
version_check_cmd: "/opt/gitlab/embedded/bin/ruby -rdigest/sha2 -e 'puts %(sha256:) + Digest::SHA256.file(%(/opt/gitlab/embedded/bin/gitaly)).hexdigest'"
)
expect(chef_run.version_file('Create version file for Gitaly')).to notify('runit_service[gitaly]').to(:hup)
end
it 'creates secret file for authenticating with GitLab' do
expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('4ecd22c031fee5c7368a5a102f76dc41')
end
it 'populates gitaly config.toml with defaults' do
expect(get_rendered_toml(chef_run, '/var/opt/gitlab/gitaly/config.toml')).to eq(
{
bin_dir: '/opt/gitlab/embedded/bin',
git: {
bin_path: '/opt/gitlab/embedded/bin/git',
ignore_gitconfig: true,
use_bundled_binaries: true
},
gitlab: {
relative_url_root: '',
url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
},
'gitlab-shell': {
dir: '/opt/gitlab/embedded/service/gitlab-shell'
},
logging: {
dir: '/var/log/gitlab/gitaly',
format: 'json'
},
prometheus_listen_addr: 'localhost:9236',
runtime_dir: '/var/opt/gitlab/gitaly/run',
socket_path: '/var/opt/gitlab/gitaly/gitaly.socket',
storage: [
{
name: 'default',
path: '/var/opt/gitlab/git-data/repositories'
}
]
}
)
end
it 'renders the runit run script with defaults' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
.with_content(%r{ulimit -n 15000})
end
it 'does not append timestamp in logs if logging format is json' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
.with_content(/svlogd \/var\/log\/gitlab\/gitaly/)
end
it 'deletes the old internal sockets directory' do
expect(chef_run).to delete_directory("/var/opt/gitlab/gitaly/internal_sockets")
end
end
context 'with a user specified GitLab secret' do
context 'when the Gitlab Shell and Gitaly secrets match' do
before do
stub_gitlab_rb(
gitaly: {
gitlab_secret: 'my-super-secret-password'
},
gitlab_shell: {
secret_token: 'my-super-secret-password'
}
)
end
it 'populates the secret file with specified value' do
expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('my-super-secret-password')
end
it 'does not log a warning' do
expect(LoggingHelper).not_to receive(:warning).with("Gitaly and GitLab Shell specifies different secrets to authenticate with GitLab")
chef_run
end
end
context 'when the Gitlab Shell and Gitaly secrets differ' do
before do
stub_gitlab_rb(
gitaly: {
gitlab_secret: 'password-for-gitaly'
},
gitlab_shell: {
secret_token: 'password-for-shell'
}
)
allow(LoggingHelper).to receive(:warning).and_call_original
end
it 'populates the secret file with specified value' do
expect(chef_run).to create_file('/var/opt/gitlab/gitaly/.gitlab_secret').with_content('password-for-gitaly')
end
it 'logs a secret mismatch warning' do
expect(LoggingHelper).to receive(:warning).with("Gitaly and GitLab Shell specifies different secrets to authenticate with GitLab")
chef_run
end
end
end
context 'log directory and runit group' do
context 'default values' do
it_behaves_like 'enabled logged service', 'gitaly', true, { log_directory_owner: 'git' }
end
context 'custom values' do
before do
stub_gitlab_rb(
gitaly: {
log_group: 'fugee'
}
)
end
it_behaves_like 'configured logrotate service', 'gitaly', 'git', 'fugee'
it_behaves_like 'enabled logged service', 'gitaly', true, { log_directory_owner: 'git', log_group: 'fugee' }
end
end
context 'sets cgroups settings' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
cgroups: {
mountpoint: cgroups_mountpoint,
hierarchy_root: cgroups_hierarchy_root,
memory_bytes: cgroups_memory_bytes,
cpu_shares: cgroups_cpu_shares,
cpu_quota_us: cgroups_cpu_quota_us,
repositories: {
count: cgroups_repositories_count,
memory_bytes: cgroups_repositories_memory_bytes,
cpu_shares: cgroups_repositories_cpu_shares,
cpu_quota_us: cgroups_repositories_cpu_quota_us,
max_cgroups_per_repo: cgroups_repositories_max_cgroups_per_repo,
}
},
},
}
)
end
it 'populate gitaly cgroups' do
cgroups_section = Regexp.new([
%r{\[cgroups\]},
%r{mountpoint = "#{cgroups_mountpoint}"},
%r{hierarchy_root = "#{cgroups_hierarchy_root}"},
%r{memory_bytes = #{cgroups_memory_bytes}},
%r{cpu_shares = #{cgroups_cpu_shares}},
%r{cpu_quota_us = #{cgroups_cpu_quota_us}},
%r{\[cgroups.repositories\]},
%r{count = #{cgroups_repositories_count}},
%r{memory_bytes = #{cgroups_repositories_memory_bytes}},
%r{cpu_shares = #{cgroups_repositories_cpu_shares}},
%r{cpu_quota_us = #{cgroups_repositories_cpu_quota_us}},
%r{max_cgroups_per_repo = #{cgroups_repositories_max_cgroups_per_repo}},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(cgroups_section)
}
end
end
context 'with Omnibus gitconfig' do
let(:omnibus_gitconfig) { nil }
let(:gitaly_gitconfig) { nil }
before do
stub_gitlab_rb(
omnibus_gitconfig: {
system: omnibus_gitconfig,
},
gitaly: {
configuration: {
git: {
config: gitaly_gitconfig,
}
}
}
)
end
context 'with default Omnibus gitconfig' do
it 'does not write a git.config section' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).not_to include("git.config")
}
end
end
context 'with default values and weird spacing' do
let(:omnibus_gitconfig) do
{
pack: ["threads =1 "],
receive: [" fsckObjects=true", "advertisePushOptions = true "],
repack: [" writeBitmaps= true "],
}
end
it 'does not write a git.config section' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).not_to include("git.config")
}
end
end
context 'with changed default value' do
let(:omnibus_gitconfig) do
{
receive: ["fsckObjects = false", "advertisePushOptions = true"],
}
end
it 'writes only non-default git.config section' do
gitconfig_section = Regexp.new([
%r{\[\[git.config\]\]},
%r{key = "receive.fsckObjects"},
%r{value = "false"},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(gitconfig_section)
expect(content).not_to include("advertisePushOptions")
}
end
end
context 'with changed default value and weird spacing' do
let(:omnibus_gitconfig) do
{
receive: ["fsckObjects = false", "advertisePushOptions=false"],
}
end
it 'writes only non-default git.config section' do
gitconfig_section = Regexp.new([
%r{\[\[git.config\]\]},
%r{key = "receive.fsckObjects"},
%r{value = "false"},
%r{},
%r{\[\[git.config\]\]},
%r{key = "receive.advertisePushOptions"},
%r{value = "false"},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(gitconfig_section)
}
end
end
context 'with mixed default and non-default values' do
let(:omnibus_gitconfig) do
{
receive: ["fsckObjects = true"],
nondefault: ["bar = baz"],
}
end
it 'writes only non-default git.config section' do
gitconfig_section = Regexp.new([
%r{\[\[git.config\]\]},
%r{key = "nondefault.bar"},
%r{value = "baz"},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(gitconfig_section)
expect(content).not_to include("fsckObjects")
}
end
end
context 'with Omnibus gitconfig containing subsections' do
let(:omnibus_gitconfig) do
{
'http "http://example.com"' => ['proxy = http://proxy.example.com'],
}
end
it 'writes the correct key' do
gitconfig_section = Regexp.new([
%r{\[\[git.config\]\]},
%r{key = "http.http://example.com.proxy"},
%r{value = "http://proxy.example.com"},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(gitconfig_section)
}
end
end
context 'with Gitaly configuration git config' do
let(:gitaly_gitconfig) do
[
{ key: "core.fsckObjects", value: "true" },
]
end
let(:omnibus_gitconfig) do
{
this: ["is = overridden"],
}
end
it 'writes only non-default git.config section' do
gitconfig_section = Regexp.new([
%r{\[\[git.config\]\]},
%r{key = "core.fsckObjects"},
%r{value = "true"},
].map(&:to_s).join('\s+'))
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(gitconfig_section)
expect(content).not_to include("overridden")
}
end
end
context 'with invalid value' do
let(:omnibus_gitconfig) do
{
receive: ["fsckObjects"]
}
end
it 'raises an error' do
expect { chef_run }.to raise_error(/Invalid entry detected in omnibus_gitconfig/)
end
end
context 'with empty Gitaly gitconfig' do
let(:gitaly_gitconfig) { [] }
let(:omnibus_gitconfig) do
{
this: ["is = overridden"],
}
end
it 'does not write a git.config section' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).not_to include("git.config")
}
end
end
end
context 'with some defaults overridden with custom configuration' do
before do
stub_gitlab_rb(
gitaly: {
enable: true,
configuration: {
socket_path: 'overridden_socket_path',
logging: {
dir: 'overridden_logging_path'
},
git: {
bin_path: 'overridden_git_bin_path'
},
custom_section: {
custom_key: 'custom_value'
},
storage: [
{
name: 'custom_storage',
path: 'custom_path'
},
],
cgroups: {
mountpoint: '/mycgroups',
hierarchy_root: 'myroot',
cpu_shares: 100,
repositories: {
count: 10
}
},
}
}
)
end
it 'renders config.toml with' do
expect(get_rendered_toml(chef_run, '/var/opt/gitlab/gitaly/config.toml')).to eq(
{
'gitlab-shell': {
dir: '/opt/gitlab/embedded/service/gitlab-shell'
},
bin_dir: '/opt/gitlab/embedded/bin',
custom_section: { custom_key: 'custom_value' },
git: {
bin_path: 'overridden_git_bin_path',
ignore_gitconfig: true,
use_bundled_binaries: true,
},
gitlab: {
url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
relative_url_root: '',
secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
},
logging: {
dir: 'overridden_logging_path',
format: 'json',
},
prometheus_listen_addr: 'localhost:9236',
runtime_dir: '/var/opt/gitlab/gitaly/run',
socket_path: 'overridden_socket_path',
storage: [
{
name: 'custom_storage',
path: 'custom_path',
}
],
cgroups: {
mountpoint: '/mycgroups',
hierarchy_root: 'myroot',
cpu_shares: 100,
repositories: {
count: 10
}
}
}
)
end
end
context 'with default settings' do
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/var/opt/gitlab/git-data/repositories"})
end
end
context 'with user settings' do
before do
stub_gitlab_rb(
gitaly: {
open_files_ulimit: open_files_ulimit,
# Sanity check that configuration values get printed out.
configuration: {
socket_path: socket_path,
listen_addr: listen_addr,
tls_listen_addr: tls_listen_addr,
string_value: 'some value',
runtime_dir: runtime_dir,
git: {
signing_key: gpg_signing_key_path,
bin_path: git_bin_path,
catfile_cache_size: git_catfile_cache_size,
use_bundled_binaries: false,
},
prometheus: {
grpc_latency_buckets: prometheus_grpc_latency_buckets
},
prometheus_listen_addr: prometheus_listen_addr,
graceful_restart_timeout: graceful_restart_timeout,
auth: {
token: auth_token,
transitioning: auth_transitioning,
},
tls: {
certificate_path: certificate_path,
key_path: key_path,
},
storage: [
{ name: 'default', path: '/tmp/path-1' },
{ name: 'nfs1', path: '/mnt/nfs1' }
],
logging: {
level: logging_level,
format: logging_format,
sentry_dsn: logging_sentry_dsn,
sentry_environment: logging_sentry_environment,
},
hooks: { custom_hooks_dir: gitaly_custom_hooks_dir },
pack_objects_cache: {
enabled: pack_objects_cache_enabled,
dir: pack_objects_cache_dir,
max_age: pack_objects_cache_max_age,
},
cgroups: {
mountpoint: cgroups_mountpoint,
hierarchy_root: cgroups_hierarchy_root,
memory_bytes: cgroups_memory_bytes,
cpu_shares: cgroups_cpu_shares,
repositories: {
count: cgroups_repositories_count,
memory_bytes: cgroups_repositories_memory_bytes,
cpu_shares: cgroups_repositories_cpu_shares,
},
},
daily_maintenance: {
disabled: daily_maintenance_disabled,
start_hour: daily_maintenance_start_hour,
start_minute: daily_maintenance_start_minute,
duration: daily_maintenance_duration,
storages: %w(storage0 storage1),
},
concurrency: [
{
rpc: '/gitaly.SmartHTTPService/PostReceivePack',
max_per_repo: 20
},
{
rpc: '/gitaly.SSHService/SSHUploadPack',
max_per_repo: 5
}
],
rate_limiting: [
{
rpc: '/gitaly.SmartHTTPService/PostReceivePack',
interval: '1s',
burst: 100
}, {
rpc: '/gitaly.SSHService/SSHUploadPack',
interval: '1s',
burst: 200,
}
],
subsection: {
array_value: [1, 2, 3]
}
}
},
gitlab_rails: {
internal_api_url: gitlab_url
},
gitlab_shell: {
http_settings: {
read_timeout: read_timeout,
user: user,
password: password,
ca_file: ca_file,
ca_path: ca_path
}
},
gitlab_workhorse: {
listen_network: 'tcp',
listen_addr: workhorse_addr,
},
user: {
username: 'foo',
group: 'bar'
}
)
end
it_behaves_like "enabled runit service", "gitaly", "root", "root"
it 'creates expected directories with correct permissions' do
expect(chef_run).to create_directory(runtime_dir).with(user: 'foo', mode: '0700')
end
it 'populates gitaly config.toml with custom values' do
expect(get_rendered_toml(chef_run, '/var/opt/gitlab/gitaly/config.toml')).to eq(
{
auth: {
token: '123#$secret456',
transitioning: true
},
bin_dir: '/opt/gitlab/embedded/bin',
cgroups: {
cpu_shares: 512,
hierarchy_root: 'gitaly',
memory_bytes: 2097152,
mountpoint: '/sys/fs/cgroup',
repositories: {
count: 10,
cpu_shares: 128,
memory_bytes: 1048576
}
},
concurrency: [
{
max_per_repo: 20,
rpc: '/gitaly.SmartHTTPService/PostReceivePack'
},
{
max_per_repo: 5,
rpc: '/gitaly.SSHService/SSHUploadPack'
}
],
daily_maintenance: {
disabled: false,
duration: '45m',
start_hour: 21,
start_minute: 9,
storages: %w(storage0 storage1)
},
git: {
bin_path: '/path/to/usr/bin/git',
catfile_cache_size: 50,
ignore_gitconfig: true,
signing_key: '/path/to/signing_key.gpg',
use_bundled_binaries: false
},
gitlab: {
'http-settings': {
ca_file: '/path/to/ca_file',
ca_path: '/path/to/ca_path',
password: 'password321',
read_timeout: 123,
user: 'user123'
},
url: 'http://localhost:3000',
secret_file: '/var/opt/gitlab/gitaly/.gitlab_secret'
},
'gitlab-shell': {
dir: '/opt/gitlab/embedded/service/gitlab-shell'
},
graceful_restart_timeout: '20m',
hooks: {
custom_hooks_dir: '/path/to/gitaly/custom/hooks'
},
listen_addr: 'localhost:7777',
logging: {
dir: '/var/log/gitlab/gitaly',
format: 'default',
level: 'warn',
sentry_dsn: 'https://my_key:my_secret@sentry.io/test_project',
sentry_environment: 'production'
},
pack_objects_cache: {
enabled: true,
dir: '/pack-objects-cache',
max_age: '10m'
},
prometheus: {
grpc_latency_buckets: [0.001, 0.005, 0.025, 0.1, 0.5, 1.0, 10.0, 30.0, 60.0, 300.0, 1500.0]
},
prometheus_listen_addr: 'localhost:9000',
rate_limiting: [
{
burst: 100,
interval: '1s',
rpc: '/gitaly.SmartHTTPService/PostReceivePack'
},
{
burst: 200,
interval: '1s',
rpc: '/gitaly.SSHService/SSHUploadPack'
}
],
runtime_dir: '/var/opt/gitlab/gitaly/user_defined/run',
socket_path: '/tmp/gitaly.socket',
storage: [
{
name: 'default',
path: '/tmp/path-1'
},
{
name: 'nfs1',
path: '/mnt/nfs1'
}
],
string_value: 'some value',
subsection: { array_value: [1, 2, 3] },
tls: {
certificate_path: '/path/to/cert.pem',
key_path: '/path/to/key.pem'
},
tls_listen_addr: 'localhost:8888',
}
)
end
it 'renders the runit run script with custom values' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
.with_content(%r{ulimit -n #{open_files_ulimit}})
end
context 'with cgroups v2' do
before do
allow(Gitaly).to receive(:cgroups_v2?).and_return true
end
it 'renders the runit run script with cgroup root creation' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run').with_content { |content|
expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}})
expect(content).to match(%r{chown -R foo:bar #{cgroups_mountpoint}})
}
end
end
context 'with cgroups v1' do
before do
allow(Gitaly).to receive(:cgroups_v2?).and_return false
end
it 'renders the runit run script with cgroup root creation' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run').with_content { |content|
expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
expect(content).to match(%r{mkdir -m 0700 -p #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/memory/#{cgroups_hierarchy_root}})
expect(content).to match(%r{chown foo:bar #{cgroups_mountpoint}/cpu/#{cgroups_hierarchy_root}})
}
end
end
it 'populates sv related log files' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/log/run')
.with_content(/svlogd -tt \/var\/log\/gitlab\/gitaly/)
end
context 'when use_wrapper is defined' do
context 'with wrapper enabled' do
before do
stub_gitlab_rb(gitaly: { use_wrapper: true })
end
it 'renders the runit run script with the wrapper' do
expect(chef_run).to render_file('/opt/gitlab/sv/gitaly/run')
.with_content(/\/opt\/gitlab\/embedded\/bin\/gitaly-wrapper/)
end
end
context 'with wrapper disabled' do
before do
stub_gitlab_rb(gitaly: { use_wrapper: false })
end
it 'renders the runit run script without the wrapper' do
expect(chef_run).not_to render_file('/opt/gitlab/sv/gitaly/run')
.with_content(/\/opt\/gitlab\/embedded\/bin\/gitaly-wrapper/)
end
end
end
context 'when storages are left to defaults' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: nil
}
},
gitlab_rails: {
repositories_storages: nil
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/var/opt/gitlab/git-data/repositories"})
end
end
context 'when storages are correctly configured' do
context 'using local gitaly' do
context 'with git_data_dirs' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: nil
}
},
git_data_dirs:
{
'default' => { 'path' => '/tmp/default/git-data' },
'nfs1' => { 'path' => '/mnt/nfs1' }
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'nfs1' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "nfs1"\s+path = "/mnt/nfs1/repositories"})
end
end
context "with gitaly['configuration']['storage']" do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: [
{
'name' => 'nfs1',
'path' => '/mnt/nfs1/repositories'
},
{
'name' => 'default',
'path' => '/tmp/default/git-data/repositories'
}
]
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'nfs1' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "nfs1"\s+path = "/mnt/nfs1/repositories"})
end
end
end
context 'using external gitaly' do
context 'with git_data_dirs' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: nil
}
},
git_data_dirs:
{
'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' })
end
end
context 'with repositories_storages' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: nil
}
},
gitlab_rails: {
repositories_storages: {
'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075', 'gitaly_token' => 'secret' },
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075', 'gitaly_token' => 'secret' })
end
end
end
# https://gitlab.com/gitlab-org/gitlab-qa/-/blob/50425989c764e135ca92a6276583581e2cffd35e/lib/gitlab/qa/scenario/test/instance/repository_storage.rb#L44
context 'using a default storage on an external gitaly' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: [
{
name: 'gitaly',
path: '/var/opt/gitlab/git-data/gitaly/repositories',
},
{
name: 'secondary',
path: '/var/opt/gitlab/git-data/secondary/repositories',
},
]
}
},
git_data_dirs:
{
'default' => {
'gitaly_address' => 'tcp://praefect.test:2305',
'gitaly_token' => 'PRAEFECT_EXTERNAL_TOKEN'
},
'gitaly' => {
'gitaly_address' => 'tcp://gitlab.test:8075',
'path' => '/var/opt/gitlab/git-data/gitaly'
},
'secondary' => {
'gitaly_address' => 'tcp://gitlab.test:8075',
'path' => '/var/opt/gitlab/git-data/secondary'
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://praefect.test:2305', 'gitaly_token' => 'PRAEFECT_EXTERNAL_TOKEN' },
'gitaly' => { 'gitaly_address' => 'tcp://gitlab.test:8075' },
'secondary' => { 'gitaly_address' => 'tcp://gitlab.test:8075' }
)
end
it 'correctly excludes external Gitaly from the storages list' do
expect(chef_run.node['gitaly']['configuration']['storage'])
.to eql([{
'name' => 'gitaly',
'path' => '/var/opt/gitlab/git-data/gitaly/repositories'
},
{
'name' => 'secondary',
'path' => '/var/opt/gitlab/git-data/secondary/repositories'
}])
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "gitaly"\s+path = "/var/opt/gitlab/git-data/gitaly/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "secondary"\s+path = "/var/opt/gitlab/git-data/secondary/repositories"})
end
end
end
context 'when different storages on separate nodes have the same path' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: [
{
"name" => "nondefault",
"path" => "/var/opt/gitlab/git-data/repositories"
}
]
}
},
git_data_dirs:
{
"default" => { "path" => "/var/opt/gitlab/git-data", "gitaly_address" => "tcp://gitaly.internal:9999" },
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://gitaly.internal:9999' },
'nondefault' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "nondefault"\s+path = "/var/opt/gitlab/git-data/repositories"})
end
end
context 'when the storages are incorrectly configured' do
context 'with multiple storages using the same path' do
let(:real_path) { Dir.mktmpdir }
let(:other_dir) { Dir.mktmpdir }
let(:symlink_path) { File.join(other_dir, 'symlink') }
context 'when Gitaly is not enabled' do
before do
File.symlink(real_path, symlink_path)
stub_gitlab_rb(
gitaly: {
enable: false,
configuration: {
storage: [
{
'name' => 'default',
'path' => '/var/opt/gitlab/git-data/repositories'
},
{
'name' => 'other',
'path' => '/var/opt/gitlab/git-data/repositories'
},
{
'name' => 'realpath',
'path' => real_path
},
{
'name' => 'symlinked',
'path' => symlink_path,
}
]
}
}
)
end
after do
FileUtils.rm_rf([real_path, other_dir])
end
it 'does not raise an error' do
expect { chef_run }.not_to raise_error
end
end
context 'when Gitaly is enabled' do
before do
File.symlink(real_path, symlink_path)
stub_gitlab_rb(
gitaly: {
configuration: {
storage: [
{
'name' => 'default',
'path' => '/var/opt/gitlab/git-data/repositories'
},
{
'name' => 'other',
'path' => '/var/opt/gitlab/git-data/repositories'
},
{
'name' => 'realpath',
'path' => real_path
},
{
'name' => 'symlinked',
'path' => symlink_path,
}
]
}
}
)
end
after do
FileUtils.rm_rf([real_path, other_dir])
end
it 'raises an error' do
expect { chef_run }.to raise_error(/Multiple Gitaly storages are sharing the same filesystem path:.*: default.*: realpath/m)
end
end
end
end
context 'when using repositories_storages and Gitaly storage configuration' do
context 'using local gitaly' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: [
{
name: 'default',
path: '/tmp/default/git-data/repositories'
},
{
name: 'nfs1',
path: '/mnt/nfs1/repositories'
}
]
}
},
gitlab_rails: {
repositories_storages: {
'default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'nfs1' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'nfs1' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "nfs1"\s+path = "/mnt/nfs1/repositories"})
end
end
context 'using external gitaly' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
storage: nil
}
},
gitlab_rails: {
repositories_storages: {
'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' })
end
end
end
end
context 'when gitaly is disabled' do
before do
stub_gitlab_rb(gitaly: { enable: false })
end
it_behaves_like "disabled runit service", "gitaly"
it 'does not create the gitaly directories' do
expect(chef_run).not_to create_directory('/var/opt/gitlab/gitaly')
expect(chef_run).not_to create_directory('/var/log/gitlab/gitaly')
expect(chef_run).not_to create_directory('/opt/gitlab/etc/gitaly')
expect(chef_run).not_to create_file('/var/opt/gitlab/gitaly/config.toml')
end
end
context 'when not using concurrency configuration' do
context 'when max_queue_size and max_queue_wait are empty' do
before do
stub_gitlab_rb(
{
gitaly: {
concurrency: [
{
'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
'max_per_repo' => 20,
}, {
'rpc' => "/gitaly.SSHService/SSHUploadPack",
'max_per_repo' => 5,
}
]
}
}
)
end
it 'populates gitaly config.toml without max_queue_size and max_queue_wait' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).not_to include("max_queue_size")
expect(content).not_to include("max_queue_wait")
}
end
end
context 'when max_per_repo is empty' do
before do
stub_gitlab_rb(
{
gitaly: {
concurrency: [
{
'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
'max_queue_size' => '10s'
}, {
'rpc' => "/gitaly.SSHService/SSHUploadPack",
'max_queue_size' => '10s'
}
]
}
}
)
end
it 'populates gitaly config.toml without max_per_repo' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).not_to include("max_per_repo")
}
end
end
context 'when max_queue_wait is set' do
before do
stub_gitlab_rb(
{
gitaly: {
configuration: {
concurrency: [
{
'rpc' => "/gitaly.SmartHTTPService/PostReceivePack",
'max_queue_wait' => "10s",
}
]
}
}
}
)
end
it 'populates gitaly config.toml with quoted max_queue_wait' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[concurrency\]\]\s+rpc = "/gitaly.SmartHTTPService/PostReceivePack"\s+max_queue_wait = "10s"})
end
end
end
context 'populates default env variables' do
it 'creates necessary env variable files' do
expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(default_vars)
end
end
context 'computes env variables based on other values' do
before do
stub_gitlab_rb(
{
user: {
home: "/my/random/path"
}
}
)
end
it 'creates necessary env variable files' do
expect(chef_run).to create_env_dir('/opt/gitlab/etc/gitaly/env').with_variables(
default_vars.merge(
{
'HOME' => '/my/random/path',
}
)
)
end
end
context 'with a non-default workhorse unix socket' do
context 'with only a listen address set' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/fake/workhorse/socket' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = "http\+unix://%2Ffake%2Fworkhorse%2Fsocket"\s+relative_url_root = ""})
end
end
context 'with only a socket directory set' do
before do
stub_gitlab_rb(gitlab_workhorse: { sockets_directory: '/fake/workhorse/sockets' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = "http\+unix://%2Ffake%2Fworkhorse%2Fsockets%2Fsocket"\s+relative_url_root = ""})
end
end
context 'with a listen_address and a sockets_directory set' do
before do
stub_gitlab_rb(gitlab_workhorse: { listen_addr: '/sockets/in/the/wind', sockets_directory: '/sockets/in/the' })
end
it 'create config file with provided values' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = "http\+unix://%2Fsockets%2Fin%2Fthe%2Fwind"\s+relative_url_root = ""})
end
end
end
context 'with a tcp workhorse listener' do
before do
stub_gitlab_rb(
external_url: 'http://example.com/gitlab',
gitlab_workhorse: {
listen_network: 'tcp',
listen_addr: 'localhost:1234'
}
)
end
it 'create config file with only the URL set' do
expect(chef_run).to render_file(config_path).with_content { |content|
expect(content).to match(%r{\[gitlab\]\s+url = "http://localhost:1234/gitlab"})
expect(content).not_to match(/relative_url_root/)
}
end
end
context 'with relative path in external_url' do
before do
stub_gitlab_rb(external_url: 'http://example.com/gitlab')
end
it 'create config file with the relative_url_root set' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = "http\+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket"\s+relative_url_root = "/gitlab"})
end
end
context 'with cgroups mountpoint and hierarchy_root' do
before do
stub_gitlab_rb(
gitaly: {
cgroups_mountpoint: '/sys/fs/cgroup',
cgroups_hierarchy_root: 'gitaly'
}
)
end
end
context 'with custom gitlab values' do
before do
stub_gitlab_rb(
gitaly: {
configuration: {
gitlab: {
url: 'http://localhost:9999',
relative_url_root: '/gitlab-ee'
}
}
}
)
end
it 'creates config file with the custom gitlab values set' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[gitlab\]\s+url = "http://localhost:9999"\s+relative_url_root = "/gitlab-ee"})
end
end
include_examples "consul service discovery", "gitaly", "gitaly"
end
RSpec.describe 'gitaly::git_data_dirs' do
let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
before do
allow(Gitlab).to receive(:[]).and_call_original
stub_gitlab_rb(gitlab_rails: {
enable: false,
}, gitaly: {
enable: true,
}, git_data_dirs: {
'default' => {
'path' => '/tmp/git-data'
}
})
end
include_examples "git data directory", "/tmp/git-data/repositories"
end
RSpec.describe 'git_data_dirs configuration' do
let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
let(:config_path) { '/var/opt/gitlab/gitaly/config.toml' }
before do
allow(Gitlab).to receive(:[]).and_call_original
end
context 'when user has not specified git_data_dir' do
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/var/opt/gitlab/git-data/repositories"})
end
end
context 'when gitaly is set to use a listen_addr' do
before do
stub_gitlab_rb(git_data_dirs: {
'default' => {
'path' => '/tmp/user/git-data'
}
}, gitaly: {
configuration: {
socket_path: '',
listen_addr: 'localhost:8123'
}
})
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tcp://localhost:8123' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/user/git-data/repositories"})
end
end
context 'when gitaly is set to use a tls_listen_addr' do
before do
stub_gitlab_rb(git_data_dirs: {
'default' => {
'path' => '/tmp/user/git-data'
}
}, gitaly: {
configuration: {
socket_path: '',
tls_listen_addr: 'localhost:8123'
}
})
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tls://localhost:8123' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/user/git-data/repositories"})
end
end
context 'when both tls and socket' do
before do
stub_gitlab_rb(git_data_dirs: {
'default' => {
'path' => '/tmp/user/git-data'
}
}, gitaly: {
configuration: {
socket_path: '/some/socket/path.socket',
tls_listen_addr: 'localhost:8123'
}
})
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages'])
.to eql('default' => { 'gitaly_address' => 'tls://localhost:8123' })
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/user/git-data/repositories"})
end
end
context 'when git_data_dirs is set to multiple directories' do
before do
stub_gitlab_rb({
git_data_dirs: {
'default' => { 'path' => '/tmp/default/git-data' },
'overflow' => { 'path' => '/tmp/other/git-overflow-data' }
}
})
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages']).to eql({
'default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'overflow' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
})
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "overflow"\s+path = "/tmp/other/git-overflow-data/repositories"})
end
end
context 'when git_data_dirs is set to multiple directories with different gitaly addresses' do
before do
stub_gitlab_rb({
git_data_dirs: {
'default' => { 'path' => '/tmp/default/git-data' },
'overflow' => { 'path' => '/tmp/other/git-overflow-data', 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123#$secret456gitaly' }
}
})
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages']).to eql({
'default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'overflow' => { 'gitaly_address' => 'tcp://localhost:8123', 'gitaly_token' => '123#$secret456gitaly' }
})
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "overflow"\s+path = "/tmp/other/git-overflow-data/repositories"})
end
end
context 'when path not defined in git_data_dirs' do
before do
stub_gitlab_rb(
{
git_data_dirs:
{
'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
}
}
)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages']).to eql({ 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' } })
end
end
context 'when git_data_dirs is set with symbol keys rather than string keys' do
before do
with_symbol_keys = {
default: { path: '/tmp/default/git-data' },
overflow: { path: '/tmp/other/git-overflow-data' }
}
allow(Gitlab).to receive(:[]).with('git_data_dirs').and_return(with_symbol_keys)
end
it 'correctly sets the repository storage directories' do
expect(chef_run.node['gitlab']['gitlab_rails']['repositories_storages']).to eql({
'default' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' },
'overflow' => { 'gitaly_address' => 'unix:/var/opt/gitlab/gitaly/gitaly.socket' }
})
end
it 'correctly populates gitaly config.toml' do
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "default"\s+path = "/tmp/default/git-data/repositories"})
expect(chef_run).to render_file(config_path)
.with_content(%r{\[\[storage\]\]\s+name = "overflow"\s+path = "/tmp/other/git-overflow-data/repositories"})
end
end
end