diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb | 176 | ||||
-rw-r--r-- | spec/features/groups/dependency_proxy_for_containers_spec.rb | 108 | ||||
-rw-r--r-- | spec/lib/gitlab/health_checks/probes/collection_spec.rb | 1 | ||||
-rw-r--r-- | spec/lib/gitlab/health_checks/redis/sessions_check_spec.rb | 8 | ||||
-rw-r--r-- | spec/lib/gitlab/instrumentation/redis_spec.rb | 3 | ||||
-rw-r--r-- | spec/lib/gitlab/middleware/multipart/handler_spec.rb | 1 | ||||
-rw-r--r-- | spec/lib/gitlab/redis/rate_limiting_spec.rb | 50 | ||||
-rw-r--r-- | spec/lib/gitlab/redis/sessions_spec.rb | 7 | ||||
-rw-r--r-- | spec/lib/gitlab/redis/trace_chunks_spec.rb | 50 | ||||
-rw-r--r-- | spec/support/redis.rb | 8 | ||||
-rw-r--r-- | spec/support/redis/redis_helpers.rb | 5 | ||||
-rw-r--r-- | spec/support/redis/redis_new_instance_shared_examples.rb | 55 |
12 files changed, 339 insertions, 133 deletions
diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb index 7415c2860c8..fa402d556c7 100644 --- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb +++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Groups::DependencyProxyForContainersController do include HttpBasicAuthHelpers include DependencyProxyHelpers + include WorkhorseHelpers let_it_be(:user) { create(:user) } let_it_be_with_reload(:group) { create(:group, :private) } @@ -242,16 +243,9 @@ RSpec.describe Groups::DependencyProxyForContainersController do end describe 'GET #blob' do - let_it_be(:blob) { create(:dependency_proxy_blob) } + let(:blob) { create(:dependency_proxy_blob, group: group) } let(:blob_sha) { blob.file_name.sub('.gz', '') } - let(:blob_response) { { status: :success, blob: blob, from_cache: false } } - - before do - allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance| - allow(instance).to receive(:execute).and_return(blob_response) - end - end subject { get_blob } @@ -264,40 +258,31 @@ RSpec.describe Groups::DependencyProxyForContainersController do it_behaves_like 'without permission' it_behaves_like 'feature flag disabled with private group' - context 'remote blob request fails' do - let(:blob_response) do - { - status: :error, - http_status: 400, - message: '' - } - end - - before do - group.add_guest(user) - end - - it 'proxies status from the remote blob request', :aggregate_failures do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - expect(response.body).to be_empty - end - end - context 'a valid user' do before do group.add_guest(user) end it_behaves_like 'a successful blob pull' - it_behaves_like 'a package tracking event', described_class.name, 'pull_blob' + it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache' - context 'with a cache entry' do - let(:blob_response) { { status: :success, blob: blob, from_cache: true } } + context 'when cache entry does not exist' do + let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' } - it_behaves_like 'returning response status', :success - it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache' + it 'returns Workhorse send-dependency instructions' do + subject + + send_data_type, send_data = workhorse_send_data + header, url = send_data.values_at('Header', 'Url') + + expect(send_data_type).to eq('send-dependency') + expect(header).to eq("Authorization" => ["Bearer abcd1234"]) + expect(url).to eq(DependencyProxy::Registry.blob_url('alpine', blob_sha)) + expect(response.headers['Content-Type']).to eq('application/gzip') + expect(response.headers['Content-Disposition']).to eq( + ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: blob.file_name) + ) + end end end @@ -319,6 +304,74 @@ RSpec.describe Groups::DependencyProxyForContainersController do it_behaves_like 'a successful blob pull' end end + + context 'when dependency_proxy_workhorse disabled' do + let(:blob_response) { { status: :success, blob: blob, from_cache: false } } + + before do + stub_feature_flags(dependency_proxy_workhorse: false) + + allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance| + allow(instance).to receive(:execute).and_return(blob_response) + end + end + + context 'remote blob request fails' do + let(:blob_response) do + { + status: :error, + http_status: 400, + message: '' + } + end + + before do + group.add_guest(user) + end + + it 'proxies status from the remote blob request', :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.body).to be_empty + end + end + + context 'a valid user' do + before do + group.add_guest(user) + end + + it_behaves_like 'a successful blob pull' + it_behaves_like 'a package tracking event', described_class.name, 'pull_blob' + + context 'with a cache entry' do + let(:blob_response) { { status: :success, blob: blob, from_cache: true } } + + it_behaves_like 'returning response status', :success + it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache' + end + end + + context 'a valid deploy token' do + let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) } + let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } + + it_behaves_like 'a successful blob pull' + + context 'pulling from a subgroup' do + let_it_be_with_reload(:parent_group) { create(:group) } + let_it_be_with_reload(:group) { create(:group, parent: parent_group) } + + before do + parent_group.create_dependency_proxy_setting!(enabled: true) + group_deploy_token.update_column(:group_id, parent_group.id) + end + + it_behaves_like 'a successful blob pull' + end + end + end end it_behaves_like 'not found when disabled' @@ -328,6 +381,61 @@ RSpec.describe Groups::DependencyProxyForContainersController do end end + describe 'GET #authorize_upload_blob' do + let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' } + + subject(:authorize_upload_blob) do + request.headers.merge!(workhorse_internal_api_request_header) + + get :authorize_upload_blob, params: { group_id: group.to_param, image: 'alpine', sha: blob_sha } + end + + it_behaves_like 'without permission' + + context 'with a valid user' do + before do + group.add_guest(user) + end + + it 'sends Workhorse file upload instructions', :aggregate_failures do + authorize_upload_blob + + expect(response.headers['Content-Type']).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response['TempPath']).to eq(DependencyProxy::FileUploader.workhorse_local_upload_path) + end + end + end + + describe 'GET #upload_blob' do + let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' } + let(:file) { fixture_file_upload("spec/fixtures/dependency_proxy/#{blob_sha}.gz", 'application/gzip') } + + subject do + request.headers.merge!(workhorse_internal_api_request_header) + + get :upload_blob, params: { + group_id: group.to_param, + image: 'alpine', + sha: blob_sha, + file: file + } + end + + it_behaves_like 'without permission' + + context 'with a valid user' do + before do + group.add_guest(user) + + expect_next_found_instance_of(Group) do |instance| + expect(instance).to receive_message_chain(:dependency_proxy_blobs, :create!) + end + end + + it_behaves_like 'a package tracking event', described_class.name, 'pull_blob' + end + end + def enable_dependency_proxy group.create_dependency_proxy_setting!(enabled: true) end diff --git a/spec/features/groups/dependency_proxy_for_containers_spec.rb b/spec/features/groups/dependency_proxy_for_containers_spec.rb new file mode 100644 index 00000000000..a4cd6d0f503 --- /dev/null +++ b/spec/features/groups/dependency_proxy_for_containers_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Group Dependency Proxy for containers', :js do + include DependencyProxyHelpers + + include_context 'file upload requests helpers' + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' } + let_it_be(:content) { fixture_file_upload("spec/fixtures/dependency_proxy/#{sha}.gz").read } + + let(:image) { 'alpine' } + let(:url) { capybara_url("/v2/#{group.full_path}/dependency_proxy/containers/#{image}/blobs/sha256:#{sha}") } + let(:token) { 'token' } + let(:headers) { { 'Authorization' => "Bearer #{build_jwt(user).encoded}" } } + + subject do + HTTParty.get(url, headers: headers) + end + + def run_server(handler) + default_server = Capybara.server + + Capybara.server = Capybara.servers[:puma] + server = Capybara::Server.new(handler) + server.boot + server + ensure + Capybara.server = default_server + end + + let_it_be(:external_server) do + handler = lambda do |env| + if env['REQUEST_PATH'] == '/token' + [200, {}, [{ token: 'token' }.to_json]] + else + [200, {}, [content]] + end + end + + run_server(handler) + end + + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) + stub_config(dependency_proxy: { enabled: true }) + group.add_developer(user) + + stub_const("DependencyProxy::Registry::AUTH_URL", external_server.base_url) + stub_const("DependencyProxy::Registry::LIBRARY_URL", external_server.base_url) + end + + shared_examples 'responds with the file' do + it 'sends file' do + expect(subject.code).to eq(200) + expect(subject.body).to eq(content) + expect(subject.headers.to_h).to include( + "content-type" => ["application/gzip"], + "content-disposition" => ["attachment; filename=\"#{sha}.gz\"; filename*=UTF-8''#{sha}.gz"], + "content-length" => ["32"] + ) + end + end + + shared_examples 'caches the file' do + it 'caches the file' do + expect { subject }.to change { + group.dependency_proxy_blobs.count + }.from(0).to(1) + + expect(subject.code).to eq(200) + expect(group.dependency_proxy_blobs.first.file.read).to eq(content) + end + end + + context 'fetching a blob' do + context 'when the blob is cached for the group' do + let!(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group) } + + it_behaves_like 'responds with the file' + + context 'dependency_proxy_workhorse feature flag disabled' do + before do + stub_feature_flags({ dependency_proxy_workhorse: false }) + end + + it_behaves_like 'responds with the file' + end + end + end + + context 'when the blob must be downloaded' do + it_behaves_like 'responds with the file' + it_behaves_like 'caches the file' + + context 'dependency_proxy_workhorse feature flag disabled' do + before do + stub_feature_flags({ dependency_proxy_workhorse: false }) + end + + it_behaves_like 'responds with the file' + it_behaves_like 'caches the file' + end + end +end diff --git a/spec/lib/gitlab/health_checks/probes/collection_spec.rb b/spec/lib/gitlab/health_checks/probes/collection_spec.rb index 401ffee9c28..741c45d953c 100644 --- a/spec/lib/gitlab/health_checks/probes/collection_spec.rb +++ b/spec/lib/gitlab/health_checks/probes/collection_spec.rb @@ -18,6 +18,7 @@ RSpec.describe Gitlab::HealthChecks::Probes::Collection do Gitlab::HealthChecks::Redis::SharedStateCheck, Gitlab::HealthChecks::Redis::TraceChunksCheck, Gitlab::HealthChecks::Redis::RateLimitingCheck, + Gitlab::HealthChecks::Redis::SessionsCheck, Gitlab::HealthChecks::GitalyCheck ] end diff --git a/spec/lib/gitlab/health_checks/redis/sessions_check_spec.rb b/spec/lib/gitlab/health_checks/redis/sessions_check_spec.rb new file mode 100644 index 00000000000..82b3b33ec0a --- /dev/null +++ b/spec/lib/gitlab/health_checks/redis/sessions_check_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative '../simple_check_shared' + +RSpec.describe Gitlab::HealthChecks::Redis::SessionsCheck do + include_examples 'simple_check', 'redis_sessions_ping', 'RedisSessions', 'PONG' +end diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb index 0da44dfb8d8..900a079cdd2 100644 --- a/spec/lib/gitlab/instrumentation/redis_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_spec.rb @@ -77,7 +77,8 @@ RSpec.describe Gitlab::Instrumentation::Redis do details_row.merge(storage: 'Queues'), details_row.merge(storage: 'SharedState'), details_row.merge(storage: 'TraceChunks'), - details_row.merge(storage: 'RateLimiting')) + details_row.merge(storage: 'RateLimiting'), + details_row.merge(storage: 'Sessions')) end end end diff --git a/spec/lib/gitlab/middleware/multipart/handler_spec.rb b/spec/lib/gitlab/middleware/multipart/handler_spec.rb index aac3f00defe..53b59b042e2 100644 --- a/spec/lib/gitlab/middleware/multipart/handler_spec.rb +++ b/spec/lib/gitlab/middleware/multipart/handler_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Gitlab::Middleware::Multipart::Handler do ::Gitlab.config.uploads.storage_path, ::JobArtifactUploader.workhorse_upload_path, ::LfsObjectUploader.workhorse_upload_path, + ::DependencyProxy::FileUploader.workhorse_upload_path, File.join(Rails.root, 'public/uploads/tmp') ] end diff --git a/spec/lib/gitlab/redis/rate_limiting_spec.rb b/spec/lib/gitlab/redis/rate_limiting_spec.rb index f15aa71a52d..e79c070df93 100644 --- a/spec/lib/gitlab/redis/rate_limiting_spec.rb +++ b/spec/lib/gitlab/redis/rate_limiting_spec.rb @@ -3,53 +3,5 @@ require 'spec_helper' RSpec.describe Gitlab::Redis::RateLimiting do - let(:instance_specific_config_file) { "config/redis.rate_limiting.yml" } - let(:environment_config_file_name) { "GITLAB_REDIS_RATE_LIMITING_CONFIG_FILE" } - let(:cache_config_file) { nil } - - before do - allow(Gitlab::Redis::Cache).to receive(:config_file_name).and_return(cache_config_file) - end - - include_examples "redis_shared_examples" - - describe '.config_file_name' do - subject { described_class.config_file_name } - - let(:rails_root) { Dir.mktmpdir('redis_shared_examples') } - - before do - # Undo top-level stub of config_file_name because we are testing that method now. - allow(described_class).to receive(:config_file_name).and_call_original - - allow(described_class).to receive(:rails_root).and_return(rails_root) - FileUtils.mkdir_p(File.join(rails_root, 'config')) - end - - after do - FileUtils.rm_rf(rails_root) - end - - context 'when there is only a resque.yml' do - before do - FileUtils.touch(File.join(rails_root, 'config/resque.yml')) - end - - it { expect(subject).to eq("#{rails_root}/config/resque.yml") } - - context 'and there is a global env override' do - before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') - end - - it { expect(subject).to eq('global override') } - - context 'and Cache has a different config file' do - let(:cache_config_file) { 'cache config file' } - - it { expect(subject).to eq('cache config file') } - end - end - end - end + include_examples "redis_new_instance_shared_examples", 'rate_limiting', Gitlab::Redis::Cache end diff --git a/spec/lib/gitlab/redis/sessions_spec.rb b/spec/lib/gitlab/redis/sessions_spec.rb new file mode 100644 index 00000000000..7e239c08e9f --- /dev/null +++ b/spec/lib/gitlab/redis/sessions_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Redis::Sessions do + include_examples "redis_new_instance_shared_examples", 'sessions', Gitlab::Redis::SharedState +end diff --git a/spec/lib/gitlab/redis/trace_chunks_spec.rb b/spec/lib/gitlab/redis/trace_chunks_spec.rb index e974dc519d6..bb3c3089430 100644 --- a/spec/lib/gitlab/redis/trace_chunks_spec.rb +++ b/spec/lib/gitlab/redis/trace_chunks_spec.rb @@ -3,53 +3,5 @@ require 'spec_helper' RSpec.describe Gitlab::Redis::TraceChunks do - let(:instance_specific_config_file) { "config/redis.trace_chunks.yml" } - let(:environment_config_file_name) { "GITLAB_REDIS_TRACE_CHUNKS_CONFIG_FILE" } - let(:shared_state_config_file) { nil } - - before do - allow(Gitlab::Redis::SharedState).to receive(:config_file_name).and_return(shared_state_config_file) - end - - include_examples "redis_shared_examples" - - describe '.config_file_name' do - subject { described_class.config_file_name } - - let(:rails_root) { Dir.mktmpdir('redis_shared_examples') } - - before do - # Undo top-level stub of config_file_name because we are testing that method now. - allow(described_class).to receive(:config_file_name).and_call_original - - allow(described_class).to receive(:rails_root).and_return(rails_root) - FileUtils.mkdir_p(File.join(rails_root, 'config')) - end - - after do - FileUtils.rm_rf(rails_root) - end - - context 'when there is only a resque.yml' do - before do - FileUtils.touch(File.join(rails_root, 'config/resque.yml')) - end - - it { expect(subject).to eq("#{rails_root}/config/resque.yml") } - - context 'and there is a global env override' do - before do - stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') - end - - it { expect(subject).to eq('global override') } - - context 'and SharedState has a different config file' do - let(:shared_state_config_file) { 'shared state config file' } - - it { expect(subject).to eq('shared state config file') } - end - end - end - end + include_examples "redis_new_instance_shared_examples", 'trace_chunks', Gitlab::Redis::SharedState end diff --git a/spec/support/redis.rb b/spec/support/redis.rb index 946c8685741..421079af8e0 100644 --- a/spec/support/redis.rb +++ b/spec/support/redis.rb @@ -46,4 +46,12 @@ RSpec.configure do |config| redis_rate_limiting_cleanup! end + + config.around(:each, :clean_gitlab_redis_sessions) do |example| + redis_sessions_cleanup! + + example.run + + redis_sessions_cleanup! + end end diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb index bf52da5d6f2..f27d873eb31 100644 --- a/spec/support/redis/redis_helpers.rb +++ b/spec/support/redis/redis_helpers.rb @@ -27,4 +27,9 @@ module RedisHelpers def redis_rate_limiting_cleanup! Gitlab::Redis::RateLimiting.with(&:flushdb) end + + # Usage: session state + def redis_sessions_cleanup! + Gitlab::Redis::Sessions.with(&:flushdb) + end end diff --git a/spec/support/redis/redis_new_instance_shared_examples.rb b/spec/support/redis/redis_new_instance_shared_examples.rb new file mode 100644 index 00000000000..e9b1e3e4da1 --- /dev/null +++ b/spec/support/redis/redis_new_instance_shared_examples.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples "redis_new_instance_shared_examples" do |name, fallback_class| + let(:instance_specific_config_file) { "config/redis.#{name}.yml" } + let(:environment_config_file_name) { "GITLAB_REDIS_#{name.upcase}_CONFIG_FILE" } + let(:fallback_config_file) { nil } + + before do + allow(fallback_class).to receive(:config_file_name).and_return(fallback_config_file) + end + + include_examples "redis_shared_examples" + + describe '.config_file_name' do + subject { described_class.config_file_name } + + let(:rails_root) { Dir.mktmpdir('redis_shared_examples') } + + before do + # Undo top-level stub of config_file_name because we are testing that method now. + allow(described_class).to receive(:config_file_name).and_call_original + + allow(described_class).to receive(:rails_root).and_return(rails_root) + FileUtils.mkdir_p(File.join(rails_root, 'config')) + end + + after do + FileUtils.rm_rf(rails_root) + end + + context 'when there is only a resque.yml' do + before do + FileUtils.touch(File.join(rails_root, 'config/resque.yml')) + end + + it { expect(subject).to eq("#{rails_root}/config/resque.yml") } + + context 'and there is a global env override' do + before do + stub_env('GITLAB_REDIS_CONFIG_FILE', 'global override') + end + + it { expect(subject).to eq('global override') } + + context "and #{fallback_class.name.demodulize} has a different config file" do + let(:fallback_config_file) { 'fallback config file' } + + it { expect(subject).to eq('fallback config file') } + end + end + end + end +end |