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.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +334 -23
  3. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +8 -0
  4. data/app/controllers/katello/api/v2/repositories_controller.rb +1 -1
  5. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
  6. data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
  7. data/app/lib/actions/katello/repository/create.rb +17 -11
  8. data/app/lib/actions/katello/repository/create_root.rb +4 -2
  9. data/app/lib/actions/katello/repository/discover.rb +11 -4
  10. data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
  11. data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
  12. data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
  13. data/app/lib/actions/pulp3/repository/create_publication.rb +4 -0
  14. data/app/lib/katello/repo_discovery.rb +4 -190
  15. data/app/lib/katello/resources/discovery/container.rb +127 -0
  16. data/app/lib/katello/resources/discovery/yum.rb +95 -0
  17. data/app/lib/katello/util/http_helper.rb +15 -0
  18. data/app/models/732bd3db9f64c621c64d2be4f2a838727aac0845.patch +61 -0
  19. data/app/models/katello/content_view.rb +2 -0
  20. data/app/models/katello/glue/pulp/repos.rb +8 -1
  21. data/app/models/katello/repository.rb +5 -1
  22. data/app/models/katello/repository.rb.bak +978 -0
  23. data/app/models/katello/root_repository.rb +14 -2
  24. data/app/models/katello/trace_status.rb +1 -1
  25. data/app/services/katello/pulp3/api/core.rb +8 -0
  26. data/app/services/katello/pulp3/api/docker.rb +4 -0
  27. data/app/services/katello/pulp3/content_view_version/import_validator.rb.bak +166 -0
  28. data/app/services/katello/pulp3/content_view_version/importable_repositories.rb.bak +164 -0
  29. data/app/services/katello/pulp3/repository/yum.rb +1 -6
  30. data/app/services/katello/repository_type.rb +1 -1
  31. data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
  32. data/config/initializers/monkeys.rb +0 -1
  33. data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
  34. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
  35. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +3 -0
  36. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +0 -3
  37. data/lib/katello/plugin.rb +12 -0
  38. data/lib/katello/repository_types/docker.rb +1 -0
  39. data/lib/katello/repository_types/yum.rb +1 -0
  40. data/lib/katello/tasks/update_repository_expiry.rake +114 -0
  41. data/lib/katello/version.rb +1 -1
  42. data/lib/katello.rb +0 -2
  43. data/locale/bn/katello.po.time_stamp +0 -0
  44. data/locale/bn_IN/katello.po.time_stamp +0 -0
  45. data/locale/ca/katello.po.time_stamp +0 -0
  46. data/locale/cs/katello.po.time_stamp +0 -0
  47. data/locale/cs_CZ/katello.po.time_stamp +0 -0
  48. data/locale/de/katello.po.time_stamp +0 -0
  49. data/locale/de_AT/katello.po.time_stamp +0 -0
  50. data/locale/de_DE/katello.po.time_stamp +0 -0
  51. data/locale/el/katello.po.time_stamp +0 -0
  52. data/locale/en/katello.po.time_stamp +0 -0
  53. data/locale/en_GB/katello.po.time_stamp +0 -0
  54. data/locale/en_US/katello.po.time_stamp +0 -0
  55. data/locale/es/katello.po.time_stamp +0 -0
  56. data/locale/et_EE/katello.po.time_stamp +0 -0
  57. data/locale/fr/katello.po.time_stamp +0 -0
  58. data/locale/gl/katello.po.time_stamp +0 -0
  59. data/locale/gu/katello.po.time_stamp +0 -0
  60. data/locale/he_IL/katello.po.time_stamp +0 -0
  61. data/locale/hi/katello.po.time_stamp +0 -0
  62. data/locale/id/katello.po.time_stamp +0 -0
  63. data/locale/it/katello.po.time_stamp +0 -0
  64. data/locale/ja/katello.po.time_stamp +0 -0
  65. data/locale/ka/katello.po.time_stamp +0 -0
  66. data/locale/kn/katello.po.time_stamp +0 -0
  67. data/locale/ko/katello.po.time_stamp +0 -0
  68. data/locale/ml_IN/katello.po.time_stamp +0 -0
  69. data/locale/mr/katello.po.time_stamp +0 -0
  70. data/locale/nl_NL/katello.po.time_stamp +0 -0
  71. data/locale/or/katello.po.time_stamp +0 -0
  72. data/locale/pa/katello.po.time_stamp +0 -0
  73. data/locale/pl/katello.po.time_stamp +0 -0
  74. data/locale/pl_PL/katello.po.time_stamp +0 -0
  75. data/locale/pt/katello.po.time_stamp +0 -0
  76. data/locale/pt_BR/katello.po.time_stamp +0 -0
  77. data/locale/ro/katello.po.time_stamp +0 -0
  78. data/locale/ro_RO/katello.po.time_stamp +0 -0
  79. data/locale/ru/katello.po.time_stamp +0 -0
  80. data/locale/sl/katello.po.time_stamp +0 -0
  81. data/locale/sv_SE/katello.po.time_stamp +0 -0
  82. data/locale/ta/katello.po.time_stamp +0 -0
  83. data/locale/ta_IN/katello.po.time_stamp +0 -0
  84. data/locale/te/katello.po.time_stamp +0 -0
  85. data/locale/tr/katello.po.time_stamp +0 -0
  86. data/locale/vi/katello.po.time_stamp +0 -0
  87. data/locale/vi_VN/katello.po.time_stamp +0 -0
  88. data/locale/zh/katello.po.time_stamp +0 -0
  89. data/locale/zh_CN/katello.po.time_stamp +0 -0
  90. data/locale/zh_TW/katello.po.time_stamp +0 -0
  91. data/package.json +0 -1
  92. data/webpack/components/Content/ContentTable.js +0 -1
  93. data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
  94. data/webpack/global_test_setup.js.bak +59 -0
  95. data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
  96. data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
  97. data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
  98. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +4 -2
  99. metadata +87 -28
  100. data/lib/monkeys/anemone.rb +0 -33
  101. data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
  102. data/webpack/utils/paramsFromHash.js +0 -16
  103. data/webpack/utils/useUrlParams.js +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd34bfe58b23b1d93a3997cebe69df8ee33721ed70f580c6e9702f7fc6ba6bd2
4
- data.tar.gz: 0c11d4179198241b8086a718fd240ae3c91af22a9912637aa88b9e2534fb11ff
3
+ metadata.gz: 7b483b80f6a5f7194a08c115f000a799cf5fbfadb5ff15a3c23425474858083e
4
+ data.tar.gz: 40c8c3a668d0cf608ad9d4c051a5eebd6fa8c1bca6f6b5ee9821ebb53aabaff1
5
5
  SHA512:
6
- metadata.gz: 8007ac10ec5431af852d6ed99534aaa40b986c2f007769a591f32d59a47afdc9f7535e850bfb6c263d9301476776a31f4b6ecc5223e76083a3923e92db66ca6e
7
- data.tar.gz: b2d7cfadb5189daa8be9bba7ddd8b4bd4b17993ed64ff2db28a87738f9bab6a7f0ef8242c2e599293307530625cb56aebc33b62f876386b58e38cbc73c50020a
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 until Katello indexes Pulp container push repositories.
13
- # before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
14
- # :push_manifest]
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 podman client and issues a 404 in that case. Podman
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['HTTP_USER_AGENT'].downcase.include?('libpod')
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
- render_error('custom_error', :status => :not_found,
427
- :locals => { :message => "Registry push not supported" })
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
- msg = "#{item} was not found!"
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 of the repository, currently 'sha1' & 'sha256' are supported")
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
- smart_proxy = ::SmartProxy.unscoped.find(input[:smart_proxy_id])
73
- ::ForemanTasks.async_task(::Actions::Katello::CapsuleContent::UpdateContentCounts, smart_proxy)
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
- create_action = plan_action(Pulp3::Orchestration::Repository::Create,
17
- repository, SmartProxy.pulp_primary, force_repo_create)
18
-
19
- return if create_action.error
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
- concurrence do
36
- plan_self(:repository_id => repository.id, :clone => clone)
37
- if !clone && repository.url.present?
38
- repository.product.alternate_content_sources.with_type(repository.content_type).each do |acs|
39
- acs.smart_proxies.each do |smart_proxy|
40
- smart_proxy_acs = ::Katello::SmartProxyAlternateContentSource.create(alternate_content_source_id: acs.id, smart_proxy_id: smart_proxy.id, repository_id: repository.id)
41
- plan_action(Pulp3::Orchestration::AlternateContentSource::Create, smart_proxy_acs)
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.relative_path = repository.custom_repo_path
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.new(input[:url], input[:content_type],
35
- input[:upstream_username], password,
36
- input[:search],
37
- output[:crawled], output[:repo_urls], output[:to_follow])
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 }
@@ -4,7 +4,7 @@ module Actions
4
4
  class BindEntitlement < Actions::Base
5
5
  def run
6
6
  output[:response] = ::Katello::Resources::Candlepin::UpstreamConsumer
7
- .bind_entitlement(pool)
7
+ .bind_entitlement(**pool)
8
8
  end
9
9
 
10
10
  def humanized_name
@@ -14,6 +14,7 @@ module Actions
14
14
  plan_action(Actions::Pulp3::OrphanCleanup::DeleteOrphanRemotes, proxy)
15
15
  end
16
16
  plan_action(Actions::Pulp3::OrphanCleanup::RemoveOrphans, proxy)
17
+ plan_action(Actions::Pulp3::OrphanCleanup::PurgeCompletedTasks, proxy)
17
18
  end
18
19
  end
19
20
  end
@@ -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