debian-mirror-gitlab/spec/lib/gitlab/workhorse_spec.rb

556 lines
18 KiB
Ruby
Raw Normal View History

2019-12-04 20:38:33 +05:30
# frozen_string_literal: true
2016-06-02 11:05:42 +05:30
require 'spec_helper'
2020-07-28 23:09:34 +05:30
RSpec.describe Gitlab::Workhorse do
2020-04-08 14:13:33 +05:30
let_it_be(:project) { create(:project, :repository) }
2021-04-29 21:17:54 +05:30
2016-11-03 12:29:30 +05:30
let(:repository) { project.repository }
def decode_workhorse_header(array)
key, value = array
command, encoded_params = value.split(":")
2020-05-24 23:13:21 +05:30
params = Gitlab::Json.parse(Base64.urlsafe_decode64(encoded_params))
2016-11-03 12:29:30 +05:30
[key, command, params]
end
2016-06-02 11:05:42 +05:30
2019-12-04 20:38:33 +05:30
before do
2021-03-11 19:13:27 +05:30
stub_feature_flags(gitaly_enforce_requests_limits: true)
2019-12-04 20:38:33 +05:30
end
2016-09-29 09:46:39 +05:30
describe ".send_git_archive" do
2018-03-17 18:26:18 +05:30
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
2020-05-24 23:13:21 +05:30
let(:path) { 'some/path' }
2019-07-31 22:56:46 +05:30
let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) }
2018-03-17 18:26:18 +05:30
let(:cache_disabled) { false }
subject do
2019-07-31 22:56:46 +05:30
described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path)
2018-03-17 18:26:18 +05:30
end
before do
allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled)
end
2020-05-24 23:13:21 +05:30
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq('Gitlab-Workhorse-Send-Data')
expect(command).to eq('git-archive')
expect(params).to eq({
'GitalyServer' => {
2021-03-11 19:13:27 +05:30
features: { 'gitaly-feature-enforce-requests-limits' => 'true' },
2020-05-24 23:13:21 +05:30
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
'ArchivePath' => metadata['ArchivePath'],
'GetArchiveRequest' => Base64.encode64(
Gitaly::GetArchiveRequest.new(
repository: repository.gitaly_repository,
commit_id: metadata['CommitId'],
prefix: metadata['ArchivePrefix'],
format: Gitaly::GetArchiveRequest::Format::ZIP,
2021-01-03 14:25:43 +05:30
path: path,
include_lfs_blobs: true
2020-05-24 23:13:21 +05:30
).to_proto
2019-07-31 22:56:46 +05:30
)
2020-05-24 23:13:21 +05:30
}.deep_stringify_keys)
2018-11-18 11:00:15 +05:30
end
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
context 'when archive caching is disabled' do
let(:cache_disabled) { true }
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
it 'tells workhorse not to use the cache' do
_, _, params = decode_workhorse_header(subject)
expect(params).to include({ 'DisableCache' => true })
2018-03-17 18:26:18 +05:30
end
end
2016-06-02 11:05:42 +05:30
context "when the repository doesn't have an archive file path" do
before do
2020-11-24 15:15:51 +05:30
allow(project.repository).to receive(:archive_metadata).and_return({})
2016-06-02 11:05:42 +05:30
end
it "raises an error" do
2018-03-17 18:26:18 +05:30
expect { subject }.to raise_error(RuntimeError)
2016-06-02 11:05:42 +05:30
end
end
end
2016-09-29 09:46:39 +05:30
2016-11-03 12:29:30 +05:30
describe '.send_git_patch' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
2020-01-01 13:55:28 +05:30
2016-11-03 12:29:30 +05:30
subject { described_class.send_git_patch(repository, diff_refs) }
2018-11-18 11:00:15 +05:30
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-format-patch")
expect(params).to eq({
'GitalyServer' => {
2021-03-11 19:13:27 +05:30
features: { 'gitaly-feature-enforce-requests-limits' => 'true' },
2018-11-18 11:00:15 +05:30
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
'RawPatchRequest' => Gitaly::RawPatchRequest.new(
repository: repository.gitaly_repository,
left_commit_id: 'base',
right_commit_id: 'head'
).to_json
}.deep_stringify_keys)
2016-11-03 12:29:30 +05:30
end
end
2019-07-07 11:18:12 +05:30
describe '.channel_websocket' do
2017-08-17 22:00:37 +05:30
def terminal(ca_pem: nil)
out = {
subprotocols: ['foo'],
url: 'wss://example.com/terminal.ws',
headers: { 'Authorization' => ['Token x'] },
max_session_time: 600
}
out[:ca_pem] = ca_pem if ca_pem
out
end
def workhorse(ca_pem: nil)
out = {
2019-07-07 11:18:12 +05:30
'Channel' => {
2017-08-17 22:00:37 +05:30
'Subprotocols' => ['foo'],
'Url' => 'wss://example.com/terminal.ws',
'Header' => { 'Authorization' => ['Token x'] },
'MaxSessionTime' => 600
}
}
2019-07-07 11:18:12 +05:30
out['Channel']['CAPem'] = ca_pem if ca_pem
2017-08-17 22:00:37 +05:30
out
end
context 'without ca_pem' do
2019-07-07 11:18:12 +05:30
subject { described_class.channel_websocket(terminal) }
2017-08-17 22:00:37 +05:30
it { is_expected.to eq(workhorse) }
end
context 'with ca_pem' do
2019-07-07 11:18:12 +05:30
subject { described_class.channel_websocket(terminal(ca_pem: "foo")) }
2017-08-17 22:00:37 +05:30
it { is_expected.to eq(workhorse(ca_pem: "foo")) }
end
end
2016-11-03 12:29:30 +05:30
describe '.send_git_diff' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
2020-01-01 13:55:28 +05:30
2018-03-17 18:26:18 +05:30
subject { described_class.send_git_diff(repository, diff_refs) }
2016-11-03 12:29:30 +05:30
2018-11-18 11:00:15 +05:30
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("git-diff")
expect(params).to eq({
'GitalyServer' => {
2021-03-11 19:13:27 +05:30
features: { 'gitaly-feature-enforce-requests-limits' => 'true' },
2018-11-18 11:00:15 +05:30
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
'RawDiffRequest' => Gitaly::RawDiffRequest.new(
repository: repository.gitaly_repository,
left_commit_id: 'base',
right_commit_id: 'head'
).to_json
}.deep_stringify_keys)
2016-11-03 12:29:30 +05:30
end
end
2016-09-29 09:46:39 +05:30
describe '#verify_api_request!' do
let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER }
let(:payload) { { 'iss' => 'gitlab-workhorse' } }
it 'accepts a correct header' do
headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
expect { call_verify(headers) }.not_to raise_error
end
it 'raises an error when the header is not set' do
expect { call_verify({}) }.to raise_jwt_error
end
it 'raises an error when the header is not signed' do
headers = { header_key => JWT.encode(payload, nil, 'none') }
expect { call_verify(headers) }.to raise_jwt_error
end
it 'raises an error when the header is signed with the wrong key' do
headers = { header_key => JWT.encode(payload, 'wrongkey', 'HS256') }
expect { call_verify(headers) }.to raise_jwt_error
end
it 'raises an error when the issuer is incorrect' do
payload['iss'] = 'somebody else'
headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') }
expect { call_verify(headers) }.to raise_jwt_error
end
def raise_jwt_error
raise_error(JWT::DecodeError)
end
def call_verify(headers)
described_class.verify_api_request!(headers)
end
end
2017-08-17 22:00:37 +05:30
describe '.git_http_ok' do
let(:user) { create(:user) }
2018-05-09 12:01:36 +05:30
let(:repo_path) { 'ignored but not allowed to be empty in gitlab-workhorse' }
2017-08-17 22:00:37 +05:30
let(:action) { 'info_refs' }
let(:params) do
2018-03-17 18:26:18 +05:30
{
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "project-#{project.id}",
ShowAllRefs: false
}
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) }
2017-08-17 22:00:37 +05:30
it { expect(subject).to include(params) }
2019-07-07 11:18:12 +05:30
context 'when the repo_type is a wiki' do
2017-08-17 22:00:37 +05:30
let(:params) do
2018-03-17 18:26:18 +05:30
{
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "wiki-#{project.id}",
ShowAllRefs: false
}
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
subject { described_class.git_http_ok(repository, Gitlab::GlRepository::WIKI, user, action) }
2017-08-17 22:00:37 +05:30
it { expect(subject).to include(params) }
end
context 'when Gitaly is enabled' do
let(:gitaly_params) do
{
2017-09-10 17:25:29 +05:30
GitalyServer: {
2021-03-11 19:13:27 +05:30
features: { 'gitaly-feature-enforce-requests-limits' => 'true' },
2017-09-10 17:25:29 +05:30
address: Gitlab::GitalyClient.address('default'),
2021-11-18 22:05:49 +05:30
token: Gitlab::GitalyClient.token('default'),
sidechannel: false
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
}
end
before do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
2021-11-18 22:05:49 +05:30
stub_feature_flags(workhorse_use_sidechannel: false)
2017-08-17 22:00:37 +05:30
end
it 'includes a Repository param' do
2018-03-17 18:26:18 +05:30
repo_param = {
2017-08-17 22:00:37 +05:30
storage_name: 'default',
2018-03-27 19:54:05 +05:30
relative_path: project.disk_path + '.git',
2018-03-17 18:26:18 +05:30
gl_repository: "project-#{project.id}"
}
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect(subject[:Repository]).to include(repo_param)
2017-08-17 22:00:37 +05:30
end
context "when git_upload_pack action is passed" do
let(:action) { 'git_upload_pack' }
let(:feature_flag) { :post_upload_pack }
2018-03-17 18:26:18 +05:30
it 'includes Gitaly params in the returned value' do
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag).and_return(true)
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
expect(subject).to include(gitaly_params)
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
context 'show_all_refs enabled' do
2019-07-07 11:18:12 +05:30
subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
it { is_expected.to include(ShowAllRefs: true) }
2017-08-17 22:00:37 +05:30
end
2021-03-11 19:13:27 +05:30
context 'when a feature flag is set for a single project' do
before do
stub_feature_flags(gitaly_mep_mep: project)
end
it 'sets the flag to true for that project' do
response = described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action)
expect(response.dig(:GitalyServer, :features)).to eq('gitaly-feature-enforce-requests-limits' => 'true',
'gitaly-feature-mep-mep' => 'true')
end
it 'sets the flag to false for other projects' do
other_project = create(:project, :public, :repository)
response = described_class.git_http_ok(other_project.repository, Gitlab::GlRepository::PROJECT, user, action)
expect(response.dig(:GitalyServer, :features)).to eq('gitaly-feature-enforce-requests-limits' => 'true',
'gitaly-feature-mep-mep' => 'false')
end
it 'sets the flag to false when there is no project' do
snippet = create(:personal_snippet, :repository)
response = described_class.git_http_ok(snippet.repository, Gitlab::GlRepository::SNIPPET, user, action)
expect(response.dig(:GitalyServer, :features)).to eq('gitaly-feature-enforce-requests-limits' => 'true',
'gitaly-feature-mep-mep' => 'false')
end
end
2017-08-17 22:00:37 +05:30
end
context "when git_receive_pack action is passed" do
let(:action) { 'git_receive_pack' }
2017-09-10 17:25:29 +05:30
it { expect(subject).to include(gitaly_params) }
2017-08-17 22:00:37 +05:30
end
context "when info_refs action is passed" do
let(:action) { 'info_refs' }
it { expect(subject).to include(gitaly_params) }
2018-03-17 18:26:18 +05:30
context 'show_all_refs enabled' do
2019-07-07 11:18:12 +05:30
subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
2018-03-17 18:26:18 +05:30
it { is_expected.to include(ShowAllRefs: true) }
end
2017-08-17 22:00:37 +05:30
end
context 'when action passed is not supported by Gitaly' do
let(:action) { 'download' }
it { expect { subject }.to raise_exception('Unsupported action: download') }
end
2021-11-18 22:05:49 +05:30
context 'when workhorse_use_sidechannel flag is set' do
context 'when a feature flag is set globally' do
before do
stub_feature_flags(workhorse_use_sidechannel: true)
end
it 'sets the flag to true' do
response = described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action)
expect(response.dig(:GitalyServer, :sidechannel)).to eq(true)
end
end
context 'when a feature flag is set for a single project' do
before do
stub_feature_flags(workhorse_use_sidechannel: project)
end
it 'sets the flag to true for that project' do
response = described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action)
expect(response.dig(:GitalyServer, :sidechannel)).to eq(true)
end
it 'sets the flag to false for other projects' do
other_project = create(:project, :public, :repository)
response = described_class.git_http_ok(other_project.repository, Gitlab::GlRepository::PROJECT, user, action)
expect(response.dig(:GitalyServer, :sidechannel)).to eq(false)
end
it 'sets the flag to false when there is no project' do
snippet = create(:personal_snippet, :repository)
response = described_class.git_http_ok(snippet.repository, Gitlab::GlRepository::SNIPPET, user, action)
expect(response.dig(:GitalyServer, :sidechannel)).to eq(false)
end
end
end
2017-08-17 22:00:37 +05:30
end
2018-11-20 20:47:30 +05:30
context 'when receive_max_input_size has been updated' do
it 'returns custom git config' do
allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
expect(subject[:GitConfigOptions]).to be_present
end
end
context 'when receive_max_input_size is empty' do
it 'returns an empty git config' do
allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { nil }
expect(subject[:GitConfigOptions]).to be_empty
end
end
2017-08-17 22:00:37 +05:30
end
describe '.set_key_and_notify' do
let(:key) { 'test-key' }
let(:value) { 'test-value' }
subject { described_class.set_key_and_notify(key, value, overwrite: overwrite) }
shared_examples 'set and notify' do
it 'set and return the same value' do
is_expected.to eq(value)
end
it 'set and notify' do
2019-10-12 21:52:04 +05:30
expect(Gitlab::Redis::SharedState).to receive(:with).and_call_original
2017-09-10 17:25:29 +05:30
expect_any_instance_of(::Redis).to receive(:publish)
2017-08-17 22:00:37 +05:30
.with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value")
subject
end
end
context 'when we set a new key' do
let(:overwrite) { true }
it_behaves_like 'set and notify'
end
context 'when we set an existing key' do
let(:old_value) { 'existing-key' }
before do
described_class.set_key_and_notify(key, old_value, overwrite: true)
end
context 'and overwrite' do
let(:overwrite) { true }
it_behaves_like 'set and notify'
end
context 'and do not overwrite' do
let(:overwrite) { false }
it 'try to set but return the previous value' do
is_expected.to eq(old_value)
end
it 'does not notify' do
2017-09-10 17:25:29 +05:30
expect_any_instance_of(::Redis).not_to receive(:publish)
2017-08-17 22:00:37 +05:30
subject
end
end
end
end
2017-09-10 17:25:29 +05:30
describe '.send_git_blob' do
include FakeBlobHelpers
let(:blob) { fake_blob }
subject { described_class.send_git_blob(repository, blob) }
2018-11-18 11:00:15 +05:30
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
2017-09-10 17:25:29 +05:30
2018-11-18 11:00:15 +05:30
expect(key).to eq('Gitlab-Workhorse-Send-Data')
expect(command).to eq('git-blob')
expect(params).to eq({
'GitalyServer' => {
2021-03-11 19:13:27 +05:30
features: { 'gitaly-feature-enforce-requests-limits' => 'true' },
2018-11-18 11:00:15 +05:30
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
'GetBlobRequest' => {
repository: repository.gitaly_repository.to_h,
oid: blob.id,
limit: -1
}
}.deep_stringify_keys)
2017-09-10 17:25:29 +05:30
end
end
2018-03-17 18:26:18 +05:30
describe '.send_url' do
let(:url) { 'http://example.com' }
subject { described_class.send_url(url) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("send-url")
expect(params).to eq({
'URL' => url,
'AllowRedirects' => false
}.deep_stringify_keys)
end
end
2018-05-09 12:01:36 +05:30
2020-10-24 23:57:45 +05:30
describe '.send_scaled_image' do
let(:location) { 'http://example.com/avatar.png' }
let(:width) { '150' }
2020-11-24 15:15:51 +05:30
let(:content_type) { 'image/png' }
2020-10-24 23:57:45 +05:30
2020-11-24 15:15:51 +05:30
subject { described_class.send_scaled_image(location, width, content_type) }
2020-10-24 23:57:45 +05:30
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("send-scaled-img")
expect(params).to eq({
'Location' => location,
2020-11-24 15:15:51 +05:30
'Width' => width,
'ContentType' => content_type
2020-10-24 23:57:45 +05:30
}.deep_stringify_keys)
end
end
2021-12-11 22:18:48 +05:30
describe '.send_dependency' do
let(:headers) { { Accept: 'foo', Authorization: 'Bearer asdf1234' } }
let(:url) { 'https://foo.bar.com/baz' }
subject { described_class.send_dependency(headers, url) }
it 'sets the header correctly', :aggregate_failures do
key, command, params = decode_workhorse_header(subject)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq("send-dependency")
expect(params).to eq({
'Header' => headers,
'Url' => url
}.deep_stringify_keys)
end
end
2018-05-09 12:01:36 +05:30
describe '.send_git_snapshot' do
let(:url) { 'http://example.com' }
subject(:request) { described_class.send_git_snapshot(repository) }
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(request)
expect(key).to eq("Gitlab-Workhorse-Send-Data")
expect(command).to eq('git-snapshot')
expect(params).to eq(
'GitalyServer' => {
2021-03-11 19:13:27 +05:30
'features' => { 'gitaly-feature-enforce-requests-limits' => 'true' },
2018-05-09 12:01:36 +05:30
'address' => Gitlab::GitalyClient.address(project.repository_storage),
'token' => Gitlab::GitalyClient.token(project.repository_storage)
},
'GetSnapshotRequest' => Gitaly::GetSnapshotRequest.new(
repository: repository.gitaly_repository
).to_json
)
end
end
2016-06-02 11:05:42 +05:30
end