From 3dbf3997bbf51eca8a313c4e152c77c1b038fd5d Mon Sep 17 00:00:00 2001 From: Steve Abrams Date: Mon, 5 Aug 2019 20:00:50 +0000 Subject: 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. --- lib/api/api.rb | 3 +- lib/api/container_registry.rb | 149 ----------------------------- lib/api/entities/container_registry.rb | 10 +- lib/api/group_container_repositories.rb | 39 ++++++++ lib/api/project_container_repositories.rb | 152 ++++++++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 154 deletions(-) delete mode 100644 lib/api/container_registry.rb create mode 100644 lib/api/group_container_repositories.rb create mode 100644 lib/api/project_container_repositories.rb (limited to 'lib') diff --git a/lib/api/api.rb b/lib/api/api.rb index 223ae13bd2d..e500a93b31e 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -104,7 +104,6 @@ module API mount ::API::BroadcastMessages mount ::API::Commits mount ::API::CommitStatuses - mount ::API::ContainerRegistry mount ::API::DeployKeys mount ::API::Deployments mount ::API::Environments @@ -116,6 +115,7 @@ module API mount ::API::GroupLabels mount ::API::GroupMilestones mount ::API::Groups + mount ::API::GroupContainerRepositories mount ::API::GroupVariables mount ::API::ImportGithub mount ::API::Internal @@ -138,6 +138,7 @@ module API mount ::API::Pipelines mount ::API::PipelineSchedules mount ::API::ProjectClusters + mount ::API::ProjectContainerRepositories mount ::API::ProjectEvents mount ::API::ProjectExport mount ::API::ProjectImport diff --git a/lib/api/container_registry.rb b/lib/api/container_registry.rb deleted file mode 100644 index 7dad20a822a..00000000000 --- a/lib/api/container_registry.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -module API - class ContainerRegistry < Grape::API - include PaginationParams - - REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( - 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 { authorize_read_container_images! } - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc 'Get a project container repositories' do - detail 'This feature was introduced in GitLab 11.8.' - success Entities::ContainerRegistry::Repository - end - params do - use :pagination - end - get ':id/registry/repositories' do - repositories = user_project.container_repositories.ordered - - present paginate(repositories), with: Entities::ContainerRegistry::Repository - end - - desc 'Delete repository' do - detail 'This feature was introduced in GitLab 11.8.' - end - params do - requires :repository_id, type: Integer, desc: 'The ID of the repository' - end - delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do - authorize_admin_container_image! - - DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) - - status :accepted - end - - desc 'Get a list of repositories tags' do - detail 'This feature was introduced in GitLab 11.8.' - success Entities::ContainerRegistry::Tag - end - params do - requires :repository_id, type: Integer, desc: 'The ID of the repository' - use :pagination - end - get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do - authorize_read_container_image! - - tags = Kaminari.paginate_array(repository.tags) - present paginate(tags), with: Entities::ContainerRegistry::Tag - end - - desc 'Delete repository tags (in bulk)' do - detail 'This feature was introduced in GitLab 11.8.' - end - params do - requires :repository_id, type: Integer, desc: 'The ID of the repository' - requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' - 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' - end - delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do - 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' - render_api_error!(message, 400) unless obtain_new_cleanup_container_lease - - CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id, - declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord - - status :accepted - end - - desc 'Get a details about repository tag' do - detail 'This feature was introduced in GitLab 11.8.' - success Entities::ContainerRegistry::TagDetails - end - params do - requires :repository_id, type: Integer, desc: 'The ID of the repository' - requires :tag_name, type: String, desc: 'The name of the tag' - end - get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do - authorize_read_container_image! - validate_tag! - - present tag, with: Entities::ContainerRegistry::TagDetails - end - - desc 'Delete repository tag' do - detail 'This feature was introduced in GitLab 11.8.' - end - params do - requires :repository_id, type: Integer, desc: 'The ID of the repository' - requires :tag_name, type: String, desc: 'The name of the tag' - end - delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do - authorize_destroy_container_image! - validate_tag! - - tag.delete - - status :ok - end - end - - helpers do - def authorize_read_container_images! - authorize! :read_container_image, user_project - end - - def authorize_read_container_image! - authorize! :read_container_image, repository - end - - def authorize_destroy_container_image! - authorize! :destroy_container_image, repository - end - - def authorize_admin_container_image! - authorize! :admin_container_image, repository - end - - def obtain_new_cleanup_container_lease - Gitlab::ExclusiveLease - .new("container_repository:cleanup_tags:#{repository.id}", - timeout: 1.hour) - .try_obtain - end - - def repository - @repository ||= user_project.container_repositories.find(params[:repository_id]) - end - - def tag - @tag ||= repository.tag(params[:tag_name]) - end - - def validate_tag! - not_found!('Tag') unless tag.valid? - end - end - end -end diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb index 00833ca7480..6250f35c7cb 100644 --- a/lib/api/entities/container_registry.rb +++ b/lib/api/entities/container_registry.rb @@ -3,18 +3,20 @@ module API module Entities module ContainerRegistry - class Repository < Grape::Entity - expose :id + class Tag < Grape::Entity expose :name expose :path expose :location - expose :created_at end - class Tag < Grape::Entity + class Repository < Grape::Entity + expose :id expose :name expose :path + expose :project_id expose :location + expose :created_at + expose :tags, using: Tag, if: -> (_, options) { options[:tags] } end class TagDetails < Tag diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb new file mode 100644 index 00000000000..fd24662cc9a --- /dev/null +++ b/lib/api/group_container_repositories.rb @@ -0,0 +1,39 @@ +# 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 diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb new file mode 100644 index 00000000000..6d53abcc500 --- /dev/null +++ b/lib/api/project_container_repositories.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +module API + class ProjectContainerRepositories < Grape::API + include PaginationParams + + REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( + 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 { authorize_read_container_images! } + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get a project container repositories' do + detail 'This feature was introduced in GitLab 11.8.' + 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_project.id, container_type: :project + ).execute + + present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags] + end + + desc 'Delete repository' do + detail 'This feature was introduced in GitLab 11.8.' + end + params do + requires :repository_id, type: Integer, desc: 'The ID of the repository' + end + delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do + authorize_admin_container_image! + + DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id) + + status :accepted + end + + desc 'Get a list of repositories tags' do + detail 'This feature was introduced in GitLab 11.8.' + success Entities::ContainerRegistry::Tag + end + params do + requires :repository_id, type: Integer, desc: 'The ID of the repository' + use :pagination + end + get ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do + authorize_read_container_image! + + tags = Kaminari.paginate_array(repository.tags) + present paginate(tags), with: Entities::ContainerRegistry::Tag + end + + desc 'Delete repository tags (in bulk)' do + detail 'This feature was introduced in GitLab 11.8.' + end + params do + requires :repository_id, type: Integer, desc: 'The ID of the repository' + requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' + 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' + end + delete ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do + 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' + render_api_error!(message, 400) unless obtain_new_cleanup_container_lease + + CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id, + declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord + + status :accepted + end + + desc 'Get a details about repository tag' do + detail 'This feature was introduced in GitLab 11.8.' + success Entities::ContainerRegistry::TagDetails + end + params do + requires :repository_id, type: Integer, desc: 'The ID of the repository' + requires :tag_name, type: String, desc: 'The name of the tag' + end + get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do + authorize_read_container_image! + validate_tag! + + present tag, with: Entities::ContainerRegistry::TagDetails + end + + desc 'Delete repository tag' do + detail 'This feature was introduced in GitLab 11.8.' + end + params do + requires :repository_id, type: Integer, desc: 'The ID of the repository' + requires :tag_name, type: String, desc: 'The name of the tag' + end + delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do + authorize_destroy_container_image! + validate_tag! + + tag.delete + + status :ok + end + end + + helpers do + def authorize_read_container_images! + authorize! :read_container_image, user_project + end + + def authorize_read_container_image! + authorize! :read_container_image, repository + end + + def authorize_destroy_container_image! + authorize! :destroy_container_image, repository + end + + def authorize_admin_container_image! + authorize! :admin_container_image, repository + end + + def obtain_new_cleanup_container_lease + Gitlab::ExclusiveLease + .new("container_repository:cleanup_tags:#{repository.id}", + timeout: 1.hour) + .try_obtain + end + + def repository + @repository ||= user_project.container_repositories.find(params[:repository_id]) + end + + def tag + @tag ||= repository.tag(params[:tag_name]) + end + + def validate_tag! + not_found!('Tag') unless tag.valid? + end + end + end +end -- cgit v1.2.1