1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# frozen_string_literal: true
module API
class ProjectContainerRepositories < ::API::Base
include PaginationParams
include ::API::Helpers::ContainerRegistryHelpers
helpers ::API::Helpers::PackagesHelpers
REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
tag_name: API::NO_SLASH_URL_PART_REGEX)
before { authorize_read_container_images! }
feature_category :container_registry
urgency :low
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
route_setting :authentication, job_token_allowed: true, job_token_scope: :project
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'List container repositories within a project' do
detail 'This feature was introduced in GitLab 11.8.'
success Entities::ContainerRegistry::Repository
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
is_array true
tags %w[container_registry]
end
params do
use :pagination
optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
optional :tags_count, type: Boolean, default: false, desc: 'Determines if the tags count should be included'
end
get ':id/registry/repositories' do
repositories = ContainerRepositoriesFinder.new(
user: current_user, subject: user_project
).execute
track_package_event('list_repositories', :container, project: user_project, namespace: user_project.namespace)
present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags], tags_count: params[:tags_count]
end
desc 'Delete repository' do
detail 'This feature was introduced in GitLab 11.8.'
success status: :accepted, message: 'Success'
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
is_array true
tags %w[container_registry]
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!
repository.delete_scheduled!
track_package_event('delete_repository', :container, project: user_project, namespace: user_project.namespace)
status :accepted
end
desc 'List tags of a repository' do
detail 'This feature was introduced in GitLab 11.8.'
success Entities::ContainerRegistry::Tag
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
is_array true
tags %w[container_registry]
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)
track_package_event('list_tags', :container, project: user_project, namespace: user_project.namespace)
present paginate(tags), with: Entities::ContainerRegistry::Tag
end
desc 'Delete repository tags (in bulk)' do
detail 'This feature was introduced in GitLab 11.8.'
success status: :accepted, message: 'Success'
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[container_registry]
end
params do
requires :repository_id, type: Integer, desc: 'The ID of the repository'
optional :name_regex_delete, type: String, untrusted_regexp: true, desc: 'The tag name regexp to delete, specify .* to delete all'
optional :name_regex, type: String, untrusted_regexp: true, desc: 'The tag name regexp to delete, specify .* to delete all'
# require either name_regex (deprecated) or name_regex_delete, it is ok to have both
at_least_one_of :name_regex, :name_regex_delete
optional :name_regex_keep, type: String, untrusted_regexp: true, desc: 'The tag name regexp to retain'
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
# rubocop:disable CodeReuse/Worker
CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id,
declared_params.except(:repository_id))
# rubocop:enable CodeReuse/Worker
track_package_event('delete_tag_bulk', :container, project: user_project, namespace: user_project.namespace)
status :accepted
end
desc 'Get details about a repository tag' do
detail 'This feature was introduced in GitLab 11.8.'
success Entities::ContainerRegistry::TagDetails
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[container_registry]
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.'
success status: :ok, message: 'Success'
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[container_registry]
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!
result = ::Projects::ContainerRepository::DeleteTagsService
.new(repository.project, current_user, tags: [declared_params[:tag_name]])
.execute(repository)
if result[:status] == :success
track_package_event('delete_tag', :container, project: user_project, namespace: user_project.namespace)
status :ok
else
status :bad_request
end
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
|