Commit 3dbf3997 authored by Steve Abrams's avatar Steve Abrams Committed by Mayra Cabrera

Add group level container repository endpoints

API endpoints for requesting container repositories
and container repositories with their tag information
are enabled for users that want to specify the group
containing the repository rather than the specific project.
parent e9918b1a
# frozen_string_literal: true
class ContainerRepositoriesFinder
# id: group or project id
# container_type: :group or :project
def initialize(id:, container_type:)
@id = id
@type = container_type.to_sym
end
def execute
if project_type?
project.container_repositories
else
group.container_repositories
end
end
private
attr_reader :id, :type
def project_type?
type == :project
end
def project
Project.find(id)
end
def group
Group.find(id)
end
end
...@@ -44,6 +44,8 @@ class Group < Namespace ...@@ -44,6 +44,8 @@ class Group < Namespace
has_many :cluster_groups, class_name: 'Clusters::Group' has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster' has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
has_many :container_repositories, through: :projects
has_many :todos has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
......
...@@ -68,6 +68,7 @@ class GroupPolicy < BasePolicy ...@@ -68,6 +68,7 @@ class GroupPolicy < BasePolicy
rule { developer }.enable :admin_milestone rule { developer }.enable :admin_milestone
rule { reporter }.policy do rule { reporter }.policy do
enable :read_container_image
enable :admin_label enable :admin_label
enable :admin_list enable :admin_list
enable :admin_issue enable :admin_issue
......
---
title: Add API endpoints to return container repositories and tags from the group
level
merge_request: 30817
author:
type: added
...@@ -6,6 +6,8 @@ This is the API docs of the [GitLab Container Registry](../user/project/containe ...@@ -6,6 +6,8 @@ This is the API docs of the [GitLab Container Registry](../user/project/containe
## List registry repositories ## List registry repositories
### Within a project
Get a list of registry repositories in a project. Get a list of registry repositories in a project.
``` ```
...@@ -14,7 +16,8 @@ GET /projects/:id/registry/repositories ...@@ -14,7 +16,8 @@ GET /projects/:id/registry/repositories
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories"
...@@ -28,6 +31,7 @@ Example response: ...@@ -28,6 +31,7 @@ Example response:
"id": 1, "id": 1,
"name": "", "name": "",
"path": "group/project", "path": "group/project",
"project_id": 9,
"location": "gitlab.example.com:5000/group/project", "location": "gitlab.example.com:5000/group/project",
"created_at": "2019-01-10T13:38:57.391Z" "created_at": "2019-01-10T13:38:57.391Z"
}, },
...@@ -35,12 +39,77 @@ Example response: ...@@ -35,12 +39,77 @@ Example response:
"id": 2, "id": 2,
"name": "releases", "name": "releases",
"path": "group/project/releases", "path": "group/project/releases",
"project_id": 9,
"location": "gitlab.example.com:5000/group/project/releases", "location": "gitlab.example.com:5000/group/project/releases",
"created_at": "2019-01-10T13:39:08.229Z" "created_at": "2019-01-10T13:39:08.229Z"
} }
] ]
``` ```
### Within a group
Get a list of registry repositories in a group.
```
GET /groups/:id/registry/repositories
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/2/registry/repositories?tags=1"
```
Example response:
```json
[
{
"id": 1,
"name": "",
"path": "group/project",
"project_id": 9,
"location": "gitlab.example.com:5000/group/project",
"created_at": "2019-01-10T13:38:57.391Z",
"tags": [
{
"name": "0.0.1",
"path": "group/project:0.0.1",
"location": "gitlab.example.com:5000/group/project:0.0.1"
}
]
},
{
"id": 2,
"name": "",
"path": "group/other_project",
"project_id": 11,
"location": "gitlab.example.com:5000/group/other_project",
"created_at": "2019-01-10T13:39:08.229Z",
"tags": [
{
"name": "0.0.1",
"path": "group/other_project:0.0.1",
"location": "gitlab.example.com:5000/group/other_project:0.0.1"
},
{
"name": "0.0.2",
"path": "group/other_project:0.0.2",
"location": "gitlab.example.com:5000/group/other_project:0.0.2"
},
{
"name": "latest",
"path": "group/other_project:latest",
"location": "gitlab.example.com:5000/group/other_project:latest"
}
]
}
]
```
## Delete registry repository ## Delete registry repository
Delete a repository in registry. Delete a repository in registry.
...@@ -62,6 +131,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git ...@@ -62,6 +131,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
## List repository tags ## List repository tags
### Within a project
Get a list of tags for given registry repository. Get a list of tags for given registry repository.
``` ```
...@@ -70,7 +141,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags ...@@ -70,7 +141,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `repository_id` | integer | yes | The ID of registry repository. | | `repository_id` | integer | yes | The ID of registry repository. |
```bash ```bash
...@@ -104,7 +175,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name ...@@ -104,7 +175,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `repository_id` | integer | yes | The ID of registry repository. | | `repository_id` | integer | yes | The ID of registry repository. |
| `tag_name` | string | yes | The name of tag. | | `tag_name` | string | yes | The name of tag. |
......
...@@ -104,7 +104,6 @@ module API ...@@ -104,7 +104,6 @@ module API
mount ::API::BroadcastMessages mount ::API::BroadcastMessages
mount ::API::Commits mount ::API::Commits
mount ::API::CommitStatuses mount ::API::CommitStatuses
mount ::API::ContainerRegistry
mount ::API::DeployKeys mount ::API::DeployKeys
mount ::API::Deployments mount ::API::Deployments
mount ::API::Environments mount ::API::Environments
...@@ -116,6 +115,7 @@ module API ...@@ -116,6 +115,7 @@ module API
mount ::API::GroupLabels mount ::API::GroupLabels
mount ::API::GroupMilestones mount ::API::GroupMilestones
mount ::API::Groups mount ::API::Groups
mount ::API::GroupContainerRepositories
mount ::API::GroupVariables mount ::API::GroupVariables
mount ::API::ImportGithub mount ::API::ImportGithub
mount ::API::Internal mount ::API::Internal
...@@ -138,6 +138,7 @@ module API ...@@ -138,6 +138,7 @@ module API
mount ::API::Pipelines mount ::API::Pipelines
mount ::API::PipelineSchedules mount ::API::PipelineSchedules
mount ::API::ProjectClusters mount ::API::ProjectClusters
mount ::API::ProjectContainerRepositories
mount ::API::ProjectEvents mount ::API::ProjectEvents
mount ::API::ProjectExport mount ::API::ProjectExport
mount ::API::ProjectImport mount ::API::ProjectImport
......
...@@ -3,18 +3,20 @@ ...@@ -3,18 +3,20 @@
module API module API
module Entities module Entities
module ContainerRegistry module ContainerRegistry
class Repository < Grape::Entity class Tag < Grape::Entity
expose :id
expose :name expose :name
expose :path expose :path
expose :location expose :location
expose :created_at
end end
class Tag < Grape::Entity class Repository < Grape::Entity
expose :id
expose :name expose :name
expose :path expose :path
expose :project_id
expose :location expose :location
expose :created_at
expose :tags, using: Tag, if: -> (_, options) { options[:tags] }
end end
class TagDetails < Tag class TagDetails < Tag
......
# frozen_string_literal: true
module API
class GroupContainerRepositories < Grape::API
include PaginationParams
before { authorize_read_group_container_images! }
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
tag_name: API::NO_SLASH_URL_PART_REGEX)
params do
requires :id, type: String, desc: "Group's ID or path"
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get a list of all repositories within a group' do
detail 'This feature was introduced in GitLab 12.2.'
success Entities::ContainerRegistry::Repository
end
params do
use :pagination
optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
end
get ':id/registry/repositories' do
repositories = ContainerRepositoriesFinder.new(
id: user_group.id, container_type: :group
).execute
present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
end
end
helpers do
def authorize_read_group_container_images!
authorize! :read_container_image, user_group
end
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
module API module API
class ContainerRegistry < Grape::API class ProjectContainerRepositories < Grape::API
include PaginationParams include PaginationParams
REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
tag_name: API::NO_SLASH_URL_PART_REGEX) tag_name: API::NO_SLASH_URL_PART_REGEX)
before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) } before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) }
...@@ -20,11 +20,14 @@ module API ...@@ -20,11 +20,14 @@ module API
end end
params do params do
use :pagination use :pagination
optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
end end
get ':id/registry/repositories' do get ':id/registry/repositories' do
repositories = user_project.container_repositories.ordered repositories = ContainerRepositoriesFinder.new(
id: user_project.id, container_type: :project
).execute
present paginate(repositories), with: Entities::ContainerRegistry::Repository present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
end end
desc 'Delete repository' do desc 'Delete repository' do
...@@ -33,7 +36,7 @@ module API ...@@ -33,7 +36,7 @@ module API
params do params do
requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :repository_id, type: Integer, desc: 'The ID of the repository'
end end
delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_admin_container_image! authorize_admin_container_image!
DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id)
...@@ -49,7 +52,7 @@ module API ...@@ -49,7 +52,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :repository_id, type: Integer, desc: 'The ID of the repository'
use :pagination use :pagination
end end
get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do get ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_read_container_image! authorize_read_container_image!
tags = Kaminari.paginate_array(repository.tags) tags = Kaminari.paginate_array(repository.tags)
...@@ -65,7 +68,7 @@ module API ...@@ -65,7 +68,7 @@ module API
optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name' optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month' optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
end end
delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do delete ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_admin_container_image! authorize_admin_container_image!
message = 'This request has already been made. You can run this at most once an hour for a given container repository' message = 'This request has already been made. You can run this at most once an hour for a given container repository'
...@@ -85,7 +88,7 @@ module API ...@@ -85,7 +88,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :repository_id, type: Integer, desc: 'The ID of the repository'
requires :tag_name, type: String, desc: 'The name of the tag' requires :tag_name, type: String, desc: 'The name of the tag'
end end
get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_read_container_image! authorize_read_container_image!
validate_tag! validate_tag!
...@@ -99,7 +102,7 @@ module API ...@@ -99,7 +102,7 @@ module API
requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :repository_id, type: Integer, desc: 'The ID of the repository'
requires :tag_name, type: String, desc: 'The name of the tag' requires :tag_name, type: String, desc: 'The name of the tag'
end end
delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
authorize_destroy_container_image! authorize_destroy_container_image!
validate_tag! validate_tag!
......
# frozen_string_literal: true
require 'spec_helper'
describe ContainerRepositoriesFinder do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:project_repository) { create(:container_repository, project: project) }
describe '#execute' do
let(:id) { nil }
subject { described_class.new(id: id, container_type: container_type).execute }
context 'when container_type is group' do
let(:other_project) { create(:project, group: group) }
let(:other_repository) do
create(:container_repository, name: 'test_repository2', project: other_project)
end
let(:container_type) { :group }
let(:id) { group.id }
it { is_expected.to match_array([project_repository, other_repository]) }
end
context 'when container_type is project' do
let(:container_type) { :project }
let(:id) { project.id }
it { is_expected.to match_array([project_repository]) }
end
context 'with invalid id' do
let(:container_type) { :project }
let(:id) { 123456789 }
it 'raises an error' do
expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
"path": { "path": {
"type": "string" "type": "string"
}, },
"project_id": {
"type": "integer"
},
"location": { "location": {
"type": "string" "type": "string"
}, },
...@@ -28,7 +31,8 @@ ...@@ -28,7 +31,8 @@
}, },
"destroy_path": { "destroy_path": {
"type": "string" "type": "string"
} },
"tags": { "$ref": "tags.json" }
}, },
"additionalProperties": false "additionalProperties": false
} }
...@@ -23,6 +23,7 @@ describe Group do ...@@ -23,6 +23,7 @@ describe Group do
it { is_expected.to have_many(:badges).class_name('GroupBadge') } it { is_expected.to have_many(:badges).class_name('GroupBadge') }
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') } it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') } it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
it { is_expected.to have_many(:container_repositories) }
describe '#members & #requesters' do describe '#members & #requesters' do
let(:requester) { create(:user) } let(:requester) { create(:user) }
......
# frozen_string_literal: true
require 'spec_helper'
describe API::GroupContainerRepositories do
set(:group) { create(:group, :private) }
set(:project) { create(:project, :private, group: group) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
let(:root_repository) { create(:container_repository, :root, project: project) }
let(:test_repository) { create(:container_repository, project: project) }
let(:users) do
{
anonymous: nil,
guest: guest,
reporter: reporter
}
end
let(:api_user) { reporter }
before do
group.add_reporter(reporter)
group.add_guest(guest)
stub_feature_flags(container_registry_api: true)
stub_container_registry_config(enabled: true)
root_repository
test_repository
end
describe 'GET /groups/:id/registry/repositories' do
let(:url) { "/groups/#{group.id}/registry/repositories" }
subject { get api(url, api_user) }
it_behaves_like 'rejected container repository access', :guest, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do
let(:object) { group }
end
context 'with invalid group id' do
let(:url) { '/groups/123412341234/registry/repositories' }
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe API::ContainerRegistry do describe API::ProjectContainerRepositories do
include ExclusiveLeaseHelpers include ExclusiveLeaseHelpers
set(:project) { create(:project, :private) } set(:project) { create(:project, :private) }
...@@ -12,6 +12,16 @@ describe API::ContainerRegistry do ...@@ -12,6 +12,16 @@ describe API::ContainerRegistry do
let(:root_repository) { create(:container_repository, :root, project: project) } let(:root_repository) { create(:container_repository, :root, project: project) }
let(:test_repository) { create(:container_repository, project: project) } let(:test_repository) { create(:container_repository, project: project) }
let(:users) do
{
anonymous: nil,
developer: developer,
guest: guest,
maintainer: maintainer,
reporter: reporter
}
end
let(:api_user) { maintainer } let(:api_user) { maintainer }
before do before do
...@@ -27,57 +37,24 @@ describe API::ContainerRegistry do ...@@ -27,57 +37,24 @@ describe API::ContainerRegistry do
test_repository test_repository
end end
shared_examples 'being disallowed' do |param|
context "for #{param}" do
let(:api_user) { public_send(param) }
it 'returns access denied' do
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context "for anonymous" do
let(:api_user) { nil }
it 'returns not found' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET /projects/:id/registry/repositories' do describe 'GET /projects/:id/registry/repositories' do
subject { get api("/projects/#{project.id}/registry/repositories", api_user) } let(:url) { "/projects/#{project.id}/registry/repositories" }
it_behaves_like 'being disallowed', :guest
context 'for reporter' do
let(:api_user) { reporter }
it 'returns a list of repositories' do
subject
expect(json_response.length).to eq(2) subject { get api(url, api_user) }
expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
root_repository.id, test_repository.id)
end
it 'returns a matching schema' do it_behaves_like 'rejected container repository access', :guest, :forbidden
subject it_behaves_like 'rejected container repository access', :anonymous, :not_found
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do
expect(response).to match_response_schema('registry/repositories') let(:object) { project }
end
end end
end end
describe 'DELETE /projects/:id/registry/repositories/:repository_id' do describe 'DELETE /projects/:id/registry/repositories/:repository_id' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) } subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) }
it_behaves_like 'being disallowed', :developer it_behaves_like 'rejected container repository access', :developer, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for maintainer' do context 'for maintainer' do
let(:api_user) { maintainer } let(:api_user) { maintainer }
...@@ -96,7 +73,8 @@ describe API::ContainerRegistry do ...@@ -96,7 +73,8 @@ describe API::ContainerRegistry do
describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do
subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) } subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) }
it_behaves_like 'being disallowed', :guest it_behaves_like 'rejected container repository access', :guest, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for reporter' do context 'for reporter' do
let(:api_user) { reporter } let(:api_user) { reporter }
...@@ -124,10 +102,13 @@ describe API::ContainerRegistry do ...@@ -124,10 +102,13 @@ describe API::ContainerRegistry do
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params } subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params }
it_behaves_like 'being disallowed', :developer do context 'disallowed' do
let(:params) do let(:params) do
{ name_regex: 'v10.*' } { name_regex: 'v10.*' }
end end
it_behaves_like 'rejected container repository access', :developer, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
end end
context 'for maintainer' do context 'for maintainer' do
...@@ -191,7 +172,8 @@ describe API::ContainerRegistry do ...@@ -191,7 +172,8 @@ describe API::ContainerRegistry do
describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
it_behaves_like 'being disallowed', :guest it_behaves_like 'rejected container repository access', :guest, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for reporter' do context 'for reporter' do
let(:api_user) { reporter } let(:api_user) { reporter }
...@@ -222,7 +204,8 @@ describe API::ContainerRegistry do ...@@ -222,7 +204,8 @@ describe API::ContainerRegistry do
describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
it_behaves_like 'being disallowed', :reporter it_behaves_like 'rejected container repository access', :reporter, :forbidden
it_behaves_like 'rejected container repository access', :anonymous, :not_found
context 'for developer' do context 'for developer' do
let(:api_user) { developer } let(:api_user) { developer }
......
...@@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do ...@@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do
read_group_merge_requests read_group_merge_requests
] ]
end end
let(:reporter_permissions) { [:admin_label] } let(:reporter_permissions) { %i[admin_label read_container_image] }
let(:developer_permissions) { [:admin_milestone] } let(:developer_permissions) { [:admin_milestone] }
let(:maintainer_permissions) do let(:maintainer_permissions) do
%i[ %i[
......
# frozen_string_literal: true
shared_examples 'rejected container repository access' do |user_type, status|
context "for #{user_type}" do
let(:api_user) { users[user_type] }
it "returns #{status}" do
subject
expect(response).to have_gitlab_http_status(status)
end
end
end
shared_examples 'returns repositories for allowed users' do |user_type, scope|
context "for #{user_type}" do
it 'returns a list of repositories' do
subject
expect(json_response.length).to eq(2)
expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
root_repository.id, test_repository.id)
expect(response.body).not_to include('tags')
end
it 'returns a matching schema' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('registry/repositories')
end
context 'with tags param' do
let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" }
before do
stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true)
stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true)
end
it 'returns a list of repositories and their tags' do
subject
expect(json_response.length).to eq(2)
expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
root_repository.id, test_repository.id)
expect(response.body).to include('tags')
end
it 'returns a matching schema' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('registry/repositories')
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment