katello 4.13.0.rc1 → 4.13.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/katello/api/registry/registry_proxies_controller.rb +334 -23
- data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +8 -0
- data/app/controllers/katello/api/v2/repositories_controller.rb +1 -1
- data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
- data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
- data/app/lib/actions/katello/repository/create.rb +17 -11
- data/app/lib/actions/katello/repository/create_root.rb +4 -2
- data/app/lib/actions/katello/repository/discover.rb +11 -4
- data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
- data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
- data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
- data/app/lib/actions/pulp3/repository/create_publication.rb +4 -0
- data/app/lib/katello/repo_discovery.rb +4 -190
- data/app/lib/katello/resources/discovery/container.rb +127 -0
- data/app/lib/katello/resources/discovery/yum.rb +95 -0
- data/app/lib/katello/util/http_helper.rb +15 -0
- data/app/models/732bd3db9f64c621c64d2be4f2a838727aac0845.patch +61 -0
- data/app/models/katello/content_view.rb +2 -0
- data/app/models/katello/glue/pulp/repos.rb +8 -1
- data/app/models/katello/repository.rb +5 -1
- data/app/models/katello/repository.rb.bak +978 -0
- data/app/models/katello/root_repository.rb +14 -2
- data/app/models/katello/trace_status.rb +1 -1
- data/app/services/katello/pulp3/api/core.rb +8 -0
- data/app/services/katello/pulp3/api/docker.rb +4 -0
- data/app/services/katello/pulp3/content_view_version/import_validator.rb.bak +166 -0
- data/app/services/katello/pulp3/content_view_version/importable_repositories.rb.bak +164 -0
- data/app/services/katello/pulp3/repository/yum.rb +1 -6
- data/app/services/katello/repository_type.rb +1 -1
- data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
- data/config/initializers/monkeys.rb +0 -1
- data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +3 -0
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +0 -3
- data/lib/katello/plugin.rb +12 -0
- data/lib/katello/repository_types/docker.rb +1 -0
- data/lib/katello/repository_types/yum.rb +1 -0
- data/lib/katello/tasks/update_repository_expiry.rake +114 -0
- data/lib/katello/version.rb +1 -1
- data/lib/katello.rb +0 -2
- data/locale/bn/katello.po.time_stamp +0 -0
- data/locale/bn_IN/katello.po.time_stamp +0 -0
- data/locale/ca/katello.po.time_stamp +0 -0
- data/locale/cs/katello.po.time_stamp +0 -0
- data/locale/cs_CZ/katello.po.time_stamp +0 -0
- data/locale/de/katello.po.time_stamp +0 -0
- data/locale/de_AT/katello.po.time_stamp +0 -0
- data/locale/de_DE/katello.po.time_stamp +0 -0
- data/locale/el/katello.po.time_stamp +0 -0
- data/locale/en/katello.po.time_stamp +0 -0
- data/locale/en_GB/katello.po.time_stamp +0 -0
- data/locale/en_US/katello.po.time_stamp +0 -0
- data/locale/es/katello.po.time_stamp +0 -0
- data/locale/et_EE/katello.po.time_stamp +0 -0
- data/locale/fr/katello.po.time_stamp +0 -0
- data/locale/gl/katello.po.time_stamp +0 -0
- data/locale/gu/katello.po.time_stamp +0 -0
- data/locale/he_IL/katello.po.time_stamp +0 -0
- data/locale/hi/katello.po.time_stamp +0 -0
- data/locale/id/katello.po.time_stamp +0 -0
- data/locale/it/katello.po.time_stamp +0 -0
- data/locale/ja/katello.po.time_stamp +0 -0
- data/locale/ka/katello.po.time_stamp +0 -0
- data/locale/kn/katello.po.time_stamp +0 -0
- data/locale/ko/katello.po.time_stamp +0 -0
- data/locale/ml_IN/katello.po.time_stamp +0 -0
- data/locale/mr/katello.po.time_stamp +0 -0
- data/locale/nl_NL/katello.po.time_stamp +0 -0
- data/locale/or/katello.po.time_stamp +0 -0
- data/locale/pa/katello.po.time_stamp +0 -0
- data/locale/pl/katello.po.time_stamp +0 -0
- data/locale/pl_PL/katello.po.time_stamp +0 -0
- data/locale/pt/katello.po.time_stamp +0 -0
- data/locale/pt_BR/katello.po.time_stamp +0 -0
- data/locale/ro/katello.po.time_stamp +0 -0
- data/locale/ro_RO/katello.po.time_stamp +0 -0
- data/locale/ru/katello.po.time_stamp +0 -0
- data/locale/sl/katello.po.time_stamp +0 -0
- data/locale/sv_SE/katello.po.time_stamp +0 -0
- data/locale/ta/katello.po.time_stamp +0 -0
- data/locale/ta_IN/katello.po.time_stamp +0 -0
- data/locale/te/katello.po.time_stamp +0 -0
- data/locale/tr/katello.po.time_stamp +0 -0
- data/locale/vi/katello.po.time_stamp +0 -0
- data/locale/vi_VN/katello.po.time_stamp +0 -0
- data/locale/zh/katello.po.time_stamp +0 -0
- data/locale/zh_CN/katello.po.time_stamp +0 -0
- data/locale/zh_TW/katello.po.time_stamp +0 -0
- data/package.json +0 -1
- data/webpack/components/Content/ContentTable.js +0 -1
- data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
- data/webpack/global_test_setup.js.bak +59 -0
- data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
- data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
- data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +4 -2
- metadata +87 -28
- data/lib/monkeys/anemone.rb +0 -33
- data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
- data/webpack/utils/paramsFromHash.js +0 -16
- data/webpack/utils/useUrlParams.js +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b483b80f6a5f7194a08c115f000a799cf5fbfadb5ff15a3c23425474858083e
|
4
|
+
data.tar.gz: 40c8c3a668d0cf608ad9d4c051a5eebd6fa8c1bca6f6b5ee9821ebb53aabaff1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4752d03a68ac8daa4499926108b811b644d29fea06dde68e8a7ebe0f3571e23d6329c23d8acc6f66eb49b675118d3a7bb7e9e00d08f499b77597f841be20c63c
|
7
|
+
data.tar.gz: ed21c09ff884e6a3b8a7e23dd2e824810507b0d84b058368c731e5479fcd9a61d778d0d7e4110db5b2ca8d6c14e9e64521b9e80660bfc06a05579b9226e366e6
|
@@ -3,15 +3,14 @@ module Katello
|
|
3
3
|
class Api::Registry::RegistryProxiesController < Api::V2::ApiController
|
4
4
|
before_action :disable_strong_params
|
5
5
|
before_action :confirm_settings
|
6
|
-
before_action :confirm_push_settings, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
|
7
|
-
:push_manifest]
|
8
6
|
skip_before_action :authorize
|
9
7
|
before_action :optional_authorize, only: [:token, :catalog]
|
10
8
|
before_action :registry_authorize, except: [:token, :v1_search, :catalog]
|
11
9
|
before_action :authorize_repository_read, only: [:pull_manifest, :tags_list]
|
12
|
-
# TODO: authorize_repository_write commented out
|
13
|
-
# before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
|
14
|
-
|
10
|
+
# TODO: authorize_repository_write commented out due to container push changes. Additional task needed to fix.
|
11
|
+
# before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
|
12
|
+
before_action :container_push_prop_validation, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
|
13
|
+
before_action :create_container_repo_if_needed, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
|
15
14
|
skip_before_action :check_media_type, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
|
16
15
|
:push_manifest]
|
17
16
|
|
@@ -85,6 +84,301 @@ module Katello
|
|
85
84
|
return false
|
86
85
|
end
|
87
86
|
|
87
|
+
def container_push_prop_validation(props = nil)
|
88
|
+
# Handle validation and repo creation for container pushes before talking to pulp
|
89
|
+
return false unless confirm_push_settings
|
90
|
+
props = parse_blob_push_props if props.nil?
|
91
|
+
return false unless check_blob_push_field_syntax(props)
|
92
|
+
|
93
|
+
# validate input and find the org and product either using downcase label or id
|
94
|
+
if props[:schema] == "label"
|
95
|
+
return false unless check_blob_push_org_label(props)
|
96
|
+
return false unless check_blob_push_product_label(props)
|
97
|
+
else
|
98
|
+
return false unless check_blob_push_org_id(props)
|
99
|
+
return false unless check_blob_push_product_id(props)
|
100
|
+
end
|
101
|
+
|
102
|
+
return false unless check_blob_push_container(props)
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_blob_push_props(path_string = nil)
|
107
|
+
# path string should follow one of these formats:
|
108
|
+
# - /v2/{org_label}/{product_label}/{name}/blobs/uploads...
|
109
|
+
# - /v2/id/{org_id}/{product_id}/{name}/blobs/uploads...
|
110
|
+
# - /v2/{org_label}/{product_label}/{name}/manifests/...
|
111
|
+
# - /v2/id/{org_id}/{product_id}/{name}/manifests/...
|
112
|
+
# inputs not matching format will return {valid_format: false}
|
113
|
+
path_string = @_request.fullpath if path_string.nil?
|
114
|
+
segments = path_string.split('/')
|
115
|
+
|
116
|
+
if segments.length >= 7 && segments[0] == "" && segments[1] == "v2" &&
|
117
|
+
segments[2] != "id" && (segments[5] == "blobs" || segments[5] == "manifests")
|
118
|
+
|
119
|
+
return {
|
120
|
+
valid_format: true,
|
121
|
+
schema: "label",
|
122
|
+
organization: segments[2],
|
123
|
+
product: segments[3],
|
124
|
+
name: segments[4]
|
125
|
+
}
|
126
|
+
elsif segments.length >= 8 && segments[0] == "" && segments[1] == "v2" &&
|
127
|
+
segments[2] == "id" && (segments[6] == "blobs" || segments[6] == "manifests")
|
128
|
+
|
129
|
+
return {
|
130
|
+
valid_format: true,
|
131
|
+
schema: "id",
|
132
|
+
organization: segments[3],
|
133
|
+
product: segments[4],
|
134
|
+
name: segments[5]
|
135
|
+
}
|
136
|
+
else
|
137
|
+
return {valid_format: false}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_blob_push_field_syntax(props)
|
142
|
+
# check basic url field syntax
|
143
|
+
unless props[:valid_format]
|
144
|
+
return render_podman_error(
|
145
|
+
"NAME_INVALID",
|
146
|
+
"Invalid format. Container pushes should follow 'organization_label/product_label/name' OR 'id/organization_id/product_id/name' schema.",
|
147
|
+
:bad_request
|
148
|
+
)
|
149
|
+
end
|
150
|
+
return true
|
151
|
+
end
|
152
|
+
|
153
|
+
# rubocop:disable Metrics/MethodLength
|
154
|
+
def check_blob_push_org_label(props)
|
155
|
+
org_label = props[:organization]
|
156
|
+
unless org_label.present? && org_label.length > 0
|
157
|
+
return render_podman_error(
|
158
|
+
"NAME_INVALID",
|
159
|
+
"Invalid format. Organization label cannot be blank.",
|
160
|
+
:bad_request
|
161
|
+
)
|
162
|
+
end
|
163
|
+
org = Organization.where("LOWER(label) = '#{org_label}'") # convert to lowercase
|
164
|
+
# reject ambiguous orgs (possible due to lowercase conversion)
|
165
|
+
if org.length > 1
|
166
|
+
# Determine if the repo already exists in one of the possible products. If yes,
|
167
|
+
# inform the user they need to destroy the existing repo and use the ID format
|
168
|
+
unless props[:product].blank? || props[:name].blank?
|
169
|
+
org.each do |o|
|
170
|
+
products = get_matching_products_from_org(o, props[:product])
|
171
|
+
products.each do |prod|
|
172
|
+
root_repos = get_root_repo_from_product(prod, props[:name])
|
173
|
+
unless root_repos.empty?
|
174
|
+
return render_podman_error(
|
175
|
+
"NAME_INVALID",
|
176
|
+
"Due to a change in your organizations, this container name has become "\
|
177
|
+
"ambiguous (org name '#{org_label}'). If you wish to continue using this "\
|
178
|
+
"container name, destroy the organization in conflict with '#{o.name} (id "\
|
179
|
+
"#{o.id}). If you wish to keep both orgs, destroy '#{o.label}/#{prod.label}/"\
|
180
|
+
"#{root_repos.first.label}' and retry your push using the id format.",
|
181
|
+
:conflict
|
182
|
+
)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Otherwise tell them to try pushing with ID format
|
189
|
+
return render_podman_error(
|
190
|
+
"NAME_INVALID",
|
191
|
+
"Organization label '#{org_label}' is ambiguous. Try using an id-based container name.",
|
192
|
+
:conflict
|
193
|
+
)
|
194
|
+
end
|
195
|
+
if org.length == 0
|
196
|
+
return render_podman_error(
|
197
|
+
"NAME_UNKNOWN",
|
198
|
+
"Organization not found: '#{org_label}'",
|
199
|
+
:not_found
|
200
|
+
)
|
201
|
+
end
|
202
|
+
@organization = org.first
|
203
|
+
true
|
204
|
+
end
|
205
|
+
|
206
|
+
def check_blob_push_org_id(props)
|
207
|
+
org_id = props[:organization]
|
208
|
+
unless org_id.present? && org_id == org_id.to_i.to_s
|
209
|
+
return render_podman_error(
|
210
|
+
"NAME_INVALID",
|
211
|
+
"Invalid format. Organization id must be an integer without leading zeros.",
|
212
|
+
:bad_request
|
213
|
+
)
|
214
|
+
end
|
215
|
+
@organization = Organization.find_by_id(org_id.to_i)
|
216
|
+
if @organization.nil?
|
217
|
+
return render_podman_error(
|
218
|
+
"NAME_UNKNOWN",
|
219
|
+
"Organization id not found: '#{org_id}'",
|
220
|
+
:not_found
|
221
|
+
)
|
222
|
+
end
|
223
|
+
true
|
224
|
+
end
|
225
|
+
|
226
|
+
def check_blob_push_product_label(props)
|
227
|
+
prod_label = props[:product]
|
228
|
+
unless prod_label.present? && prod_label.length > 0
|
229
|
+
return render_podman_error(
|
230
|
+
"NAME_INVALID",
|
231
|
+
"Invalid format. Product label cannot be blank.",
|
232
|
+
:bad_request
|
233
|
+
)
|
234
|
+
end
|
235
|
+
product = get_matching_products_from_org(@organization, prod_label)
|
236
|
+
# reject ambiguous products (possible due to lowercase conversion)
|
237
|
+
if product.length > 1
|
238
|
+
# Determine if the repo already exists in one of the possible products. If yes,
|
239
|
+
# inform the user they need to destroy the existing repo and use the ID format
|
240
|
+
unless props[:name].blank?
|
241
|
+
product.each do |prod|
|
242
|
+
root_repos = get_root_repo_from_product(prod, props[:name])
|
243
|
+
unless root_repos.empty?
|
244
|
+
return render_podman_error(
|
245
|
+
"NAME_INVALID",
|
246
|
+
"Due to a change in your products, this container name has become ambiguous "\
|
247
|
+
"(product name '#{prod_label}'). If you wish to continue using this container "\
|
248
|
+
"name, destroy the product in conflict with '#{prod.name}' (id #{prod.id}). If "\
|
249
|
+
"you wish to keep both products, destroy '#{@organization.label}/#{prod.label}/"\
|
250
|
+
"#{root_repos.first.label}' and retry your push using the id format.",
|
251
|
+
:conflict
|
252
|
+
)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
return render_podman_error(
|
258
|
+
"NAME_INVALID",
|
259
|
+
"Product label '#{prod_label}' is ambiguous. Try using an id-based container name.",
|
260
|
+
:conflict
|
261
|
+
)
|
262
|
+
end
|
263
|
+
if product.length == 0
|
264
|
+
return render_podman_error(
|
265
|
+
"NAME_UNKNOWN",
|
266
|
+
"Product not found: '#{prod_label}'",
|
267
|
+
:not_found
|
268
|
+
)
|
269
|
+
end
|
270
|
+
@product = product.first
|
271
|
+
true
|
272
|
+
end
|
273
|
+
|
274
|
+
def check_blob_push_product_id(props)
|
275
|
+
prod_id = props[:product]
|
276
|
+
unless prod_id.present? && prod_id == prod_id.to_i.to_s
|
277
|
+
return render_podman_error(
|
278
|
+
"NAME_INVALID",
|
279
|
+
"Invalid format. Product id must be an integer without leading zeros.",
|
280
|
+
:bad_request
|
281
|
+
)
|
282
|
+
end
|
283
|
+
@product = @organization.products.find_by_id(prod_id.to_i)
|
284
|
+
if @product.nil?
|
285
|
+
return render_podman_error(
|
286
|
+
"NAME_UNKNOWN",
|
287
|
+
"Product id not found: '#{prod_id}'",
|
288
|
+
:not_found
|
289
|
+
)
|
290
|
+
end
|
291
|
+
true
|
292
|
+
end
|
293
|
+
|
294
|
+
def get_matching_products_from_org(organization, product_label)
|
295
|
+
return organization.products.where("LOWER(label) = '#{product_label}'") # convert to lowercase
|
296
|
+
end
|
297
|
+
|
298
|
+
def get_root_repo_from_product(product, root_repo_name)
|
299
|
+
return product.root_repositories.where(label: root_repo_name)
|
300
|
+
end
|
301
|
+
|
302
|
+
def check_blob_push_container(props)
|
303
|
+
unless props[:name].present? && props[:name].length > 0
|
304
|
+
return render_podman_error(
|
305
|
+
"NAME_INVALID",
|
306
|
+
"Invalid format. Container name cannot be blank.",
|
307
|
+
:bad_request
|
308
|
+
)
|
309
|
+
end
|
310
|
+
|
311
|
+
@container_name = props[:name]
|
312
|
+
@container_push_name_format = props[:schema]
|
313
|
+
if @container_push_name_format == "label"
|
314
|
+
@container_path_input = "#{props[:organization]}/#{props[:product]}/#{props[:name]}"
|
315
|
+
else
|
316
|
+
@container_path_input = "id/#{props[:organization]}/#{props[:product]}/#{props[:name]}"
|
317
|
+
end
|
318
|
+
|
319
|
+
# If the repo already exists, check if the existing push format matches
|
320
|
+
root_repo = get_root_repo_from_product(@product, @container_name).first
|
321
|
+
if !root_repo.nil? && @container_push_name_format != root_repo.container_push_name_format
|
322
|
+
return render_podman_error(
|
323
|
+
"NAME_INVALID",
|
324
|
+
"Repository name '#{@container_name}' already exists in this product using a different naming scheme. Please retry your request with the #{root_repo.container_push_name_format} format or destroy and recreate the repository using your preferred schema.",
|
325
|
+
:conflict
|
326
|
+
)
|
327
|
+
end
|
328
|
+
|
329
|
+
true
|
330
|
+
end
|
331
|
+
|
332
|
+
def create_container_repo_if_needed
|
333
|
+
if get_root_repo_from_product(@product, @container_name).empty?
|
334
|
+
root = @product.add_repo(
|
335
|
+
name: @container_name,
|
336
|
+
label: @container_name,
|
337
|
+
download_policy: 'immediate',
|
338
|
+
content_type: Repository::DOCKER_TYPE,
|
339
|
+
unprotected: true,
|
340
|
+
is_container_push: true,
|
341
|
+
container_push_name: @container_path_input,
|
342
|
+
container_push_name_format: @container_push_name_format
|
343
|
+
)
|
344
|
+
sync_task(::Actions::Katello::Repository::CreateRoot, root, @container_path_input)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def blob_push_cleanup
|
349
|
+
# after manifest upload, index content and set version href using pulp api
|
350
|
+
root_repo = get_root_repo_from_product(@product, @container_name)&.first
|
351
|
+
instance_repo = root_repo&.library_instance
|
352
|
+
|
353
|
+
unless root_repo.present? && instance_repo.present?
|
354
|
+
return render_podman_error(
|
355
|
+
"BLOB_UPLOAD_UNKNOWN",
|
356
|
+
"Could not locate local uploaded repository for content indexing.",
|
357
|
+
:not_found
|
358
|
+
)
|
359
|
+
end
|
360
|
+
|
361
|
+
api = ::Katello::Pulp3::Repository.api(SmartProxy.pulp_primary, ::Katello::Repository::DOCKER_TYPE).container_push_api
|
362
|
+
api_response = api.list(name: @container_path_input)&.results&.first
|
363
|
+
latest_version_href = api_response&.latest_version_href
|
364
|
+
pulp_href = api_response&.pulp_href
|
365
|
+
|
366
|
+
if latest_version_href.empty? || pulp_href.empty?
|
367
|
+
return render_podman_error(
|
368
|
+
"BLOB_UPLOAD_UNKNOWN",
|
369
|
+
"Could not locate repository properties for content indexing.",
|
370
|
+
:not_found
|
371
|
+
)
|
372
|
+
end
|
373
|
+
|
374
|
+
instance_repo.update!(version_href: latest_version_href)
|
375
|
+
::Katello::Pulp3::RepositoryReference.where(root_repository_id: instance_repo.root_id,
|
376
|
+
content_view_id: instance_repo.content_view.id, repository_href: pulp_href).create!
|
377
|
+
instance_repo.index_content
|
378
|
+
|
379
|
+
true
|
380
|
+
end
|
381
|
+
|
88
382
|
def find_writable_repository
|
89
383
|
Repository.docker_type.syncable.find_by_container_repository_name(params[:repository])
|
90
384
|
end
|
@@ -205,17 +499,6 @@ module Katello
|
|
205
499
|
redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers, max_redirects: 0) }
|
206
500
|
end
|
207
501
|
|
208
|
-
def push_manifest
|
209
|
-
headers = translated_headers_for_proxy
|
210
|
-
headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
|
211
|
-
body = @_request.body.read
|
212
|
-
pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, body, headers)
|
213
|
-
pulp_response.headers.each do |key, value|
|
214
|
-
response.header[key.to_s] = value
|
215
|
-
end
|
216
|
-
head pulp_response.code
|
217
|
-
end
|
218
|
-
|
219
502
|
def start_upload_blob
|
220
503
|
headers = translated_headers_for_proxy
|
221
504
|
headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
|
@@ -225,6 +508,7 @@ module Katello
|
|
225
508
|
pulp_response.headers.each do |key, value|
|
226
509
|
response.header[key.to_s] = value
|
227
510
|
end
|
511
|
+
|
228
512
|
head pulp_response.code
|
229
513
|
end
|
230
514
|
|
@@ -268,6 +552,21 @@ module Katello
|
|
268
552
|
head pulp_response.code
|
269
553
|
end
|
270
554
|
|
555
|
+
def push_manifest
|
556
|
+
headers = translated_headers_for_proxy
|
557
|
+
headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
|
558
|
+
body = @_request.body.read
|
559
|
+
pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, body, headers)
|
560
|
+
pulp_response.headers.each do |key, value|
|
561
|
+
response.header[key.to_s] = value
|
562
|
+
end
|
563
|
+
|
564
|
+
cleanup_result = blob_push_cleanup if pulp_response.code.between?(200, 299)
|
565
|
+
return false unless cleanup_result
|
566
|
+
|
567
|
+
head pulp_response.code
|
568
|
+
end
|
569
|
+
|
271
570
|
def ping
|
272
571
|
response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
|
273
572
|
render json: {}, status: :ok
|
@@ -278,10 +577,10 @@ module Katello
|
|
278
577
|
end
|
279
578
|
|
280
579
|
def v1_search
|
281
|
-
# Checks for
|
580
|
+
# Checks for v2 client and issues a 404 in that case. Podman
|
282
581
|
# examines the response from a /v1_search request. If the result
|
283
582
|
# is a 4XX, it will then proceed with a request to /_catalog
|
284
|
-
if request.headers['
|
583
|
+
if request.headers['HTTP_DOCKER_DISTRIBUTION_API_VERSION'] == 'registry/2.0'
|
285
584
|
render json: {}, status: :not_found
|
286
585
|
return
|
287
586
|
end
|
@@ -423,8 +722,11 @@ module Katello
|
|
423
722
|
|
424
723
|
def confirm_push_settings
|
425
724
|
return true if SETTINGS.dig(:katello, :container_image_registry, :allow_push)
|
426
|
-
|
427
|
-
|
725
|
+
render_podman_error(
|
726
|
+
"UNSUPPORTED",
|
727
|
+
"Registry push is not enabled. To enable, add ':katello:'->':container_image_registry:'->':allow_push: true' in the katello settings file.",
|
728
|
+
:unprocessable_entity
|
729
|
+
)
|
428
730
|
end
|
429
731
|
|
430
732
|
def request_url
|
@@ -446,10 +748,19 @@ module Katello
|
|
446
748
|
Rails.logger.debug "With body: #{filter_sensitive_data(response.body)}\n" unless route_name == 'pull_blob'
|
447
749
|
end
|
448
750
|
|
751
|
+
def render_podman_error(code, message, status = :bad_request)
|
752
|
+
# Renders a podman-compatible error and returns false.
|
753
|
+
# code: uppercase string code from opencontainer error code spec:
|
754
|
+
# https://specs.opencontainers.org/distribution-spec/?v=v1.0.0#DISTRIBUTION-SPEC-140
|
755
|
+
# message: a custom error string
|
756
|
+
# status: a symbol in the 400 block of the rails response code table:
|
757
|
+
# https://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option
|
758
|
+
render json: {errors: [{code: code, message: message}]}, status: status
|
759
|
+
false
|
760
|
+
end
|
761
|
+
|
449
762
|
def item_not_found(item)
|
450
|
-
|
451
|
-
# returning errors based on registry specifications in https://docs.docker.com/registry/spec/api/#errors
|
452
|
-
render json: {errors: [code: :invalid_request, message: msg, details: msg]}, status: :not_found
|
763
|
+
render_podman_error("NAME_UNKNOWN", "#{item} was not found!", :not_found)
|
453
764
|
end
|
454
765
|
end
|
455
766
|
end
|
@@ -259,6 +259,14 @@ module Katello
|
|
259
259
|
def facts
|
260
260
|
User.current = User.anonymous_admin
|
261
261
|
@host.update_candlepin_associations(rhsm_params)
|
262
|
+
if params[:environments]
|
263
|
+
new_envs = params[:environments].map do |env|
|
264
|
+
get_content_view_environment("cp_id", env['id'])
|
265
|
+
end
|
266
|
+
new_envs.compact!
|
267
|
+
Rails.logger.debug "Setting new content view environments for host #{@host.to_label}: #{new_envs.map(&:label)}"
|
268
|
+
@host.content_facet.content_view_environments = new_envs
|
269
|
+
end
|
262
270
|
update_host_registered_through(@host, request.headers)
|
263
271
|
@host.refresh_statuses([::Katello::RhelLifecycleStatus])
|
264
272
|
render :json => {:content => _("Facts successfully updated.")}, :status => :ok
|
@@ -48,7 +48,7 @@ Pass [] to make repo available for clients regardless of OS version. Maximum len
|
|
48
48
|
param :ssl_client_cert_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Cert"), :allow_nil => true
|
49
49
|
param :ssl_client_key_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Key"), :allow_nil => true
|
50
50
|
param :unprotected, :bool, :desc => N_("true if this repository can be published via HTTP")
|
51
|
-
param :checksum_type, String, :desc => N_("Checksum
|
51
|
+
param :checksum_type, String, :desc => N_("Checksum used for published repository contents. Supported types: %s") % Katello::RootRepository::CHECKSUM_TYPES.join(', ')
|
52
52
|
param :docker_upstream_name, String, :desc => N_("Name of the upstream docker repository")
|
53
53
|
param :include_tags, Array, :desc => N_("Comma-separated list of tags to sync for a container image repository")
|
54
54
|
param :exclude_tags, Array, :desc => N_("Comma-separated list of tags to exclude when syncing a container image repository. Default: any tag ending in \"-source\"")
|
@@ -69,8 +69,13 @@ module Actions
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def update_content_counts(_execution_plan)
|
72
|
-
|
73
|
-
|
72
|
+
if Setting[:automatic_content_count_updates]
|
73
|
+
smart_proxy = ::SmartProxy.unscoped.find(input[:smart_proxy_id])
|
74
|
+
::ForemanTasks.async_task(::Actions::Katello::CapsuleContent::UpdateContentCounts, smart_proxy)
|
75
|
+
else
|
76
|
+
Rails.logger.info "Skipping content counts update as automatic content count updates are disabled. To enable automatic content count updates, set the 'automatic_content_count_updates' setting to true.
|
77
|
+
To update content counts manually, run the 'Update Content Counts' action."
|
78
|
+
end
|
74
79
|
end
|
75
80
|
|
76
81
|
def resource_locks
|
@@ -16,10 +16,15 @@ module Actions
|
|
16
16
|
repositories.each do |repo|
|
17
17
|
plan_action(Katello::Repository::RefreshRepository, repo)
|
18
18
|
end
|
19
|
-
plan_self
|
19
|
+
plan_self(:organization_name => organization.name)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
def run
|
24
|
+
organization = ::Organization.find_by(name: input[:organization_name])
|
25
|
+
organization&.manifest_expiration_date(cached: false) # update organization.manifest_imported? value
|
26
|
+
end
|
27
|
+
|
23
28
|
def failure_notification(plan)
|
24
29
|
::Katello::UINotifications::Subscriptions::ManifestDeleteError.deliver!(
|
25
30
|
:subject => subject_organization,
|
@@ -13,10 +13,13 @@ module Actions
|
|
13
13
|
|
14
14
|
org = repository.organization
|
15
15
|
sequence do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
# Container push repositories will already be in pulp. The version_href is
|
17
|
+
# directly updated after a push.
|
18
|
+
unless root.is_container_push
|
19
|
+
create_action = plan_action(Pulp3::Orchestration::Repository::Create,
|
20
|
+
repository, SmartProxy.pulp_primary, force_repo_create)
|
21
|
+
return if create_action.error
|
22
|
+
end
|
20
23
|
|
21
24
|
# when creating a clone, the following actions are handled by the
|
22
25
|
# publish/promote process
|
@@ -32,13 +35,16 @@ module Actions
|
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
# Container push repos do not need metadata generation or ACS (they do not sync)
|
39
|
+
unless root.is_container_push
|
40
|
+
concurrence do
|
41
|
+
plan_self(:repository_id => repository.id, :clone => clone)
|
42
|
+
if !clone && repository.url.present?
|
43
|
+
repository.product.alternate_content_sources.with_type(repository.content_type).each do |acs|
|
44
|
+
acs.smart_proxies.each do |smart_proxy|
|
45
|
+
smart_proxy_acs = ::Katello::SmartProxyAlternateContentSource.create(alternate_content_source_id: acs.id, smart_proxy_id: smart_proxy.id, repository_id: repository.id)
|
46
|
+
plan_action(Pulp3::Orchestration::AlternateContentSource::Create, smart_proxy_acs)
|
47
|
+
end
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|
@@ -2,13 +2,15 @@ module Actions
|
|
2
2
|
module Katello
|
3
3
|
module Repository
|
4
4
|
class CreateRoot < Actions::EntryAction
|
5
|
-
def plan(root)
|
5
|
+
def plan(root, relative_path = nil)
|
6
6
|
root.save!
|
7
7
|
repository = ::Katello::Repository.new(:environment => root.organization.library,
|
8
8
|
:content_view_version => root.organization.library.default_content_view_version,
|
9
9
|
:root => root)
|
10
|
-
repository.
|
10
|
+
repository.container_repository_name = relative_path
|
11
|
+
repository.relative_path = relative_path || repository.custom_repo_path
|
11
12
|
repository.save!
|
13
|
+
|
12
14
|
action_subject(repository)
|
13
15
|
plan_action(::Actions::Katello::Repository::Create, repository)
|
14
16
|
end
|
@@ -31,10 +31,17 @@ module Actions
|
|
31
31
|
(on nil do
|
32
32
|
unless output[:to_follow].empty?
|
33
33
|
password = decrypt_field(input[:upstream_password])
|
34
|
-
repo_discovery = ::Katello::RepoDiscovery.
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
repo_discovery = ::Katello::RepoDiscovery.class_for(input[:content_type]).new(
|
35
|
+
input[:url],
|
36
|
+
output[:crawled],
|
37
|
+
output[:repo_urls],
|
38
|
+
output[:to_follow],
|
39
|
+
{
|
40
|
+
upstream_username: input[:upstream_username],
|
41
|
+
upstream_password: password,
|
42
|
+
search: input[:search]
|
43
|
+
}
|
44
|
+
)
|
38
45
|
|
39
46
|
repo_discovery.run(output[:to_follow].shift)
|
40
47
|
suspend { |suspended_action| world.clock.ping suspended_action, 0.001 }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Actions
|
2
|
+
module Pulp3
|
3
|
+
module OrphanCleanup
|
4
|
+
class PurgeCompletedTasks < Pulp3::AbstractAsyncTask
|
5
|
+
def plan(smart_proxy)
|
6
|
+
plan_self(:smart_proxy_id => smart_proxy.id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
output[:pulp_tasks] = ::Katello::Pulp3::Api::Core.new(smart_proxy).purge_completed_tasks
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -14,6 +14,10 @@ module Actions
|
|
14
14
|
def invoke_external_task
|
15
15
|
unless input[:skip_publication_creation]
|
16
16
|
repository = ::Katello::Repository.find(input[:repository_id])
|
17
|
+
if repository.root.sha1_checksum?
|
18
|
+
repository.root.remove_sha1_checksum_type
|
19
|
+
repository.root.save!
|
20
|
+
end
|
17
21
|
output[:response] = repository.backend_service(smart_proxy).with_mirror_adapter.create_publication
|
18
22
|
end
|
19
23
|
end
|