2019-07-07 11:18:12 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
RSpec.describe ContainerRepository do
|
2017-08-17 22:00:37 +05:30
|
|
|
let(:group) { create(:group, name: 'group') }
|
|
|
|
let(:project) { create(:project, path: 'test', group: group) }
|
|
|
|
|
|
|
|
let(:repository) do
|
|
|
|
create(:container_repository, name: 'my_image', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: true,
|
|
|
|
api_url: 'http://registry.gitlab',
|
|
|
|
host_port: 'registry.gitlab')
|
|
|
|
|
|
|
|
stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list')
|
2019-09-30 21:07:59 +05:30
|
|
|
.with(headers: { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') })
|
2017-08-17 22:00:37 +05:30
|
|
|
.to_return(
|
|
|
|
status: 200,
|
2020-05-24 23:13:21 +05:30
|
|
|
body: Gitlab::Json.dump(tags: ['test_tag']),
|
2017-08-17 22:00:37 +05:30
|
|
|
headers: { 'Content-Type' => 'application/json' })
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'associations' do
|
|
|
|
it 'belongs to the project' do
|
|
|
|
expect(repository).to belong_to(:project)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
describe '.exists_by_path?' do
|
|
|
|
it 'returns true for known container repository paths' do
|
|
|
|
path = ContainerRegistry::Path.new("#{project.full_path}/#{repository.name}")
|
|
|
|
expect(described_class.exists_by_path?(path)).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false for unknown container repository paths' do
|
|
|
|
path = ContainerRegistry::Path.new('you/dont/know/me')
|
|
|
|
expect(described_class.exists_by_path?(path)).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
describe '#tag' do
|
|
|
|
it 'has a test tag' do
|
|
|
|
expect(repository.tag('test')).not_to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#path' do
|
|
|
|
context 'when project path does not contain uppercase letters' do
|
|
|
|
it 'returns a full path to the repository' do
|
|
|
|
expect(repository.path).to eq('group/test/my_image')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when path contains uppercase letters' do
|
2017-09-10 17:25:29 +05:30
|
|
|
let(:project) { create(:project, :repository, path: 'MY_PROJECT', group: group) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
it 'returns a full path without capital letters' do
|
|
|
|
expect(repository.path).to eq('group/my_project/my_image')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#manifest' do
|
|
|
|
it 'returns non-empty manifest' do
|
|
|
|
expect(repository.manifest).not_to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#valid?' do
|
|
|
|
it 'is a valid repository' do
|
|
|
|
expect(repository).to be_valid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#tags' do
|
|
|
|
it 'returns non-empty tags list' do
|
|
|
|
expect(repository.tags).not_to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
describe '#tags_count' do
|
|
|
|
it 'returns the count of tags' do
|
|
|
|
expect(repository.tags_count).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
describe '#has_tags?' do
|
|
|
|
it 'has tags' do
|
|
|
|
expect(repository).to have_tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#delete_tags!' do
|
|
|
|
let(:repository) do
|
|
|
|
create(:container_repository, name: 'my_image',
|
2019-12-21 20:55:43 +05:30
|
|
|
tags: { latest: '123', rc1: '234' },
|
2017-08-17 22:00:37 +05:30
|
|
|
project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when action succeeds' do
|
|
|
|
it 'returns status that indicates success' do
|
|
|
|
expect(repository.client)
|
2020-03-13 15:44:24 +05:30
|
|
|
.to receive(:delete_repository_tag_by_digest)
|
2019-12-21 20:55:43 +05:30
|
|
|
.twice
|
2017-08-17 22:00:37 +05:30
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect(repository.delete_tags!).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when action fails' do
|
|
|
|
it 'returns status that indicates failure' do
|
|
|
|
expect(repository.client)
|
2020-03-13 15:44:24 +05:30
|
|
|
.to receive(:delete_repository_tag_by_digest)
|
2019-12-21 20:55:43 +05:30
|
|
|
.twice
|
2017-08-17 22:00:37 +05:30
|
|
|
.and_return(false)
|
|
|
|
|
|
|
|
expect(repository.delete_tags!).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-03-13 15:44:24 +05:30
|
|
|
describe '#delete_tag_by_name' do
|
|
|
|
let(:repository) do
|
|
|
|
create(:container_repository, name: 'my_image',
|
|
|
|
tags: { latest: '123', rc1: '234' },
|
|
|
|
project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when action succeeds' do
|
|
|
|
it 'returns status that indicates success' do
|
|
|
|
expect(repository.client)
|
|
|
|
.to receive(:delete_repository_tag_by_name)
|
|
|
|
.with(repository.path, "latest")
|
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect(repository.delete_tag_by_name('latest')).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when action fails' do
|
|
|
|
it 'returns status that indicates failure' do
|
|
|
|
expect(repository.client)
|
|
|
|
.to receive(:delete_repository_tag_by_name)
|
|
|
|
.with(repository.path, "latest")
|
|
|
|
.and_return(false)
|
|
|
|
|
|
|
|
expect(repository.delete_tag_by_name('latest')).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
describe '#location' do
|
|
|
|
context 'when registry is running on a custom port' do
|
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: true,
|
|
|
|
api_url: 'http://registry.gitlab:5000',
|
|
|
|
host_port: 'registry.gitlab:5000')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns a full location of the repository' do
|
|
|
|
expect(repository.location)
|
|
|
|
.to eq 'registry.gitlab:5000/group/test/my_image'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#root_repository?' do
|
|
|
|
context 'when repository is a root repository' do
|
|
|
|
let(:repository) { create(:container_repository, :root) }
|
|
|
|
|
|
|
|
it 'returns true' do
|
|
|
|
expect(repository).to be_root_repository
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when repository is not a root repository' do
|
|
|
|
it 'returns false' do
|
|
|
|
expect(repository).not_to be_root_repository
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-03 14:25:43 +05:30
|
|
|
describe '#start_expiration_policy!' do
|
|
|
|
subject { repository.start_expiration_policy! }
|
|
|
|
|
|
|
|
it 'sets the expiration policy started at to now' do
|
2021-02-22 17:27:13 +05:30
|
|
|
freeze_time do
|
2021-01-03 14:25:43 +05:30
|
|
|
expect { subject }
|
|
|
|
.to change { repository.expiration_policy_started_at }.from(nil).to(Time.zone.now)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#reset_expiration_policy_started_at!' do
|
|
|
|
subject { repository.reset_expiration_policy_started_at! }
|
|
|
|
|
|
|
|
before do
|
|
|
|
repository.start_expiration_policy!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets the expiration policy started at' do
|
|
|
|
started_at = repository.expiration_policy_started_at
|
|
|
|
|
|
|
|
expect(started_at).not_to be_nil
|
|
|
|
expect { subject }
|
|
|
|
.to change { repository.expiration_policy_started_at }.from(started_at).to(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
describe '.build_from_path' do
|
|
|
|
let(:registry_path) do
|
|
|
|
ContainerRegistry::Path.new(project.full_path + '/some/image')
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:repository) do
|
|
|
|
described_class.build_from_path(registry_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates repository assigned to a correct project' do
|
|
|
|
expect(repository.project).to eq project
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates repository with a correct name' do
|
|
|
|
expect(repository.name).to eq 'some/image'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is not persisted' do
|
|
|
|
expect(repository).not_to be_persisted
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.create_from_path!' do
|
|
|
|
let(:repository) do
|
|
|
|
described_class.create_from_path!(ContainerRegistry::Path.new(path))
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:repository_path) { ContainerRegistry::Path.new(path) }
|
|
|
|
|
|
|
|
context 'when received multi-level repository path' do
|
|
|
|
let(:path) { project.full_path + '/some/image' }
|
|
|
|
|
|
|
|
it 'fabricates repository assigned to a correct project' do
|
|
|
|
expect(repository.project).to eq project
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates repository with a correct name' do
|
|
|
|
expect(repository.name).to eq 'some/image'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when path is too long' do
|
|
|
|
let(:path) do
|
|
|
|
project.full_path + '/a/b/c/d/e/f/g/h/i/j/k/l/n/o/p/s/t/u/x/y/z'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create repository and raises error' do
|
|
|
|
expect { repository }.to raise_error(
|
|
|
|
ContainerRegistry::Path::InvalidRegistryPathError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when received multi-level repository with nested groups' do
|
|
|
|
let(:group) { create(:group, :nested, name: 'nested') }
|
|
|
|
let(:path) { project.full_path + '/some/image' }
|
|
|
|
|
|
|
|
it 'fabricates repository assigned to a correct project' do
|
|
|
|
expect(repository.project).to eq project
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates repository with a correct name' do
|
|
|
|
expect(repository.name).to eq 'some/image'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has path including a nested group' do
|
|
|
|
expect(repository.path).to include 'nested/test/some/image'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when received root repository path' do
|
|
|
|
let(:path) { project.full_path }
|
|
|
|
|
|
|
|
it 'fabricates repository assigned to a correct project' do
|
|
|
|
expect(repository.project).to eq project
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates repository with an empty name' do
|
|
|
|
expect(repository.name).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.build_root_repository' do
|
|
|
|
let(:repository) do
|
|
|
|
described_class.build_root_repository(project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'fabricates a root repository object' do
|
|
|
|
expect(repository).to be_root_repository
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'assignes it to the correct project' do
|
|
|
|
expect(repository.project).to eq project
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not persist it' do
|
|
|
|
expect(repository).not_to be_persisted
|
|
|
|
end
|
|
|
|
end
|
2019-12-26 22:10:19 +05:30
|
|
|
|
|
|
|
describe '.for_group_and_its_subgroups' do
|
|
|
|
subject { described_class.for_group_and_its_subgroups(test_group) }
|
|
|
|
|
|
|
|
context 'in a group' do
|
|
|
|
let(:test_group) { group }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(repository) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a subgroup' do
|
|
|
|
let(:test_group) { create(:group) }
|
|
|
|
let(:another_project) { create(:project, path: 'test', group: test_group) }
|
|
|
|
|
|
|
|
let(:another_repository) do
|
|
|
|
create(:container_repository, name: 'my_image', project: another_project)
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
group.parent = test_group
|
|
|
|
group.save
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(repository, another_repository) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'group without container_repositories' do
|
|
|
|
let(:test_group) { create(:group) }
|
|
|
|
|
|
|
|
it { is_expected.to eq([]) }
|
|
|
|
end
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
describe '.search_by_name' do
|
|
|
|
let!(:another_repository) do
|
|
|
|
create(:container_repository, name: 'my_foo_bar', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { described_class.search_by_name('my_image') }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(repository) }
|
|
|
|
end
|
2021-01-29 00:20:46 +05:30
|
|
|
|
|
|
|
describe '.for_project_id' do
|
|
|
|
subject { described_class.for_project_id(project.id) }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(repository) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.waiting_for_cleanup' do
|
|
|
|
let_it_be(:repository_cleanup_scheduled) { create(:container_repository, :cleanup_scheduled) }
|
|
|
|
let_it_be(:repository_cleanup_unfinished) { create(:container_repository, :cleanup_unfinished) }
|
|
|
|
let_it_be(:repository_cleanup_ongoing) { create(:container_repository, :cleanup_ongoing) }
|
|
|
|
|
|
|
|
subject { described_class.waiting_for_cleanup }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(repository_cleanup_scheduled, repository_cleanup_unfinished) }
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|