katello 4.13.0.rc1 → 4.13.0
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/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/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/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/root_repository.rb +4 -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/repository/yum.rb +1 -6
- data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
- data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
- data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +10 -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/new/views/new-repository.html +0 -3
- data/lib/katello/plugin.rb +12 -0
- data/lib/katello/version.rb +1 -1
- 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/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 +7 -7
- 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: 24a3d52ddfd156ad5a11c37f63def11242d86b6c67f7e7b5e97cbad375ec0ffa
|
4
|
+
data.tar.gz: f307cdc997b68aac82c1f7a9b5d9e380eb9c8a93dcacd7e87a3a05dd6c5589c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c188709e872b9e152963f860462b267a480533e7634d5804708838df3701cade9ec0dc8d9c0bc72423bccd93383947a7b1d5a4a624a03245a4b8784c9c73a36e
|
7
|
+
data.tar.gz: 3caf9ffe2eda0fb1f1f3593f1e2badcc92946a483c16f448421193faf067960b73380c81bc2349a6c9bef4b64550ee6410afbf926157b0d132533897614dfc6d
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -454,6 +454,8 @@ module Katello
|
|
454
454
|
).each do |facet|
|
455
455
|
facet.update_applicability_counts
|
456
456
|
facet.update_errata_status
|
457
|
+
rescue NoMethodError
|
458
|
+
Rails.logger.warn _('Errata statuses not updated for deleted content facet with UUID %s') % facet.uuid
|
457
459
|
end
|
458
460
|
end
|
459
461
|
end
|
@@ -133,7 +133,14 @@ module Katello
|
|
133
133
|
|
134
134
|
repo_param[:mirroring_policy] = Katello::RootRepository::MIRRORING_POLICY_ADDITIVE if repo_param[:mirroring_policy].blank?
|
135
135
|
|
136
|
-
|
136
|
+
repo_param = repo_param.merge(:product_id => self.id)
|
137
|
+
|
138
|
+
# Container push may concurrently call root add several times before the db can update.
|
139
|
+
if repo_param[:is_container_push]
|
140
|
+
RootRepository.create_or_find_by!(repo_param)
|
141
|
+
else
|
142
|
+
RootRepository.new(repo_param)
|
143
|
+
end
|
137
144
|
end
|
138
145
|
end
|
139
146
|
end
|
@@ -118,7 +118,7 @@ module Katello
|
|
118
118
|
end}
|
119
119
|
|
120
120
|
before_validation :set_pulp_id
|
121
|
-
before_validation :set_container_repository_name, :
|
121
|
+
before_validation :set_container_repository_name, :unless => :skip_container_name?
|
122
122
|
|
123
123
|
scope :has_url, -> { joins(:root).where.not("#{RootRepository.table_name}.url" => nil) }
|
124
124
|
scope :on_demand, -> { joins(:root).where("#{RootRepository.table_name}.download_policy" => ::Katello::RootRepository::DOWNLOAD_ON_DEMAND) }
|
@@ -268,6 +268,10 @@ module Katello
|
|
268
268
|
self.content_view_version.content_view
|
269
269
|
end
|
270
270
|
|
271
|
+
def skip_container_name?
|
272
|
+
self.library_instance? && self.root.docker? && self.root.is_container_push
|
273
|
+
end
|
274
|
+
|
271
275
|
def library_instance?
|
272
276
|
self.content_view.default?
|
273
277
|
end
|
@@ -17,7 +17,7 @@ module Katello
|
|
17
17
|
DOWNLOAD_POLICIES = [DOWNLOAD_IMMEDIATE, DOWNLOAD_ON_DEMAND].freeze
|
18
18
|
|
19
19
|
IGNORABLE_CONTENT_UNIT_TYPES = %w(srpm treeinfo).freeze
|
20
|
-
CHECKSUM_TYPES = %w(
|
20
|
+
CHECKSUM_TYPES = %w(sha256 sha384 sha512).freeze
|
21
21
|
|
22
22
|
SUBSCRIBABLE_TYPES = [Repository::YUM_TYPE, Repository::OSTREE_TYPE, Repository::DEB_TYPE].freeze
|
23
23
|
SKIPABLE_METADATA_TYPES = [Repository::YUM_TYPE, Repository::DEB_TYPE].freeze
|
@@ -112,6 +112,8 @@ module Katello
|
|
112
112
|
only_integer: true
|
113
113
|
}
|
114
114
|
|
115
|
+
validates :container_push_name_format, inclusion: { in: ['label', 'id'].freeze, allow_nil: true}
|
116
|
+
|
115
117
|
scope :subscribable, -> { where(content_type: RootRepository::SUBSCRIBABLE_TYPES) }
|
116
118
|
scope :skipable_metadata_check, -> { where(content_type: RootRepository::SKIPABLE_METADATA_TYPES) }
|
117
119
|
scope :has_url, -> { where.not(:url => nil) }
|
@@ -195,7 +197,7 @@ module Katello
|
|
195
197
|
def ensure_docker_repo_unprotected
|
196
198
|
unless unprotected
|
197
199
|
errors.add(:base, _("Container Image Repositories are not protected at this time. " \
|
198
|
-
|
200
|
+
"They need to be published via http to be available to containers."))
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
@@ -36,7 +36,7 @@ module Katello
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def to_status(_options = {})
|
39
|
-
traces = host.host_traces.pluck(:app_type)
|
39
|
+
traces = host.host_traces.reload.pluck(:app_type)
|
40
40
|
traces.delete(Katello::HostTracer::TRACE_APP_TYPE_SESSION)
|
41
41
|
|
42
42
|
if traces.include?(Katello::HostTracer::TRACE_APP_TYPE_STATIC)
|
@@ -176,6 +176,14 @@ module Katello
|
|
176
176
|
nil
|
177
177
|
end
|
178
178
|
|
179
|
+
def purge_class
|
180
|
+
PulpcoreClient::Purge
|
181
|
+
end
|
182
|
+
|
183
|
+
def purge_completed_tasks
|
184
|
+
tasks_api.purge(purge_class.new(finished_before: DateTime.now - Setting[:completed_pulp_task_protection_days]))
|
185
|
+
end
|
186
|
+
|
179
187
|
def delete_orphans
|
180
188
|
[orphans_api.cleanup(PulpcoreClient::OrphansCleanup.new(orphan_protection_time: (smart_proxy.pulp_mirror? ? 0 : Setting[:orphan_protection_time])))]
|
181
189
|
end
|
@@ -22,12 +22,7 @@ module Katello
|
|
22
22
|
|
23
23
|
def publication_options(repository_version)
|
24
24
|
options = super(repository_version)
|
25
|
-
options.merge(
|
26
|
-
{
|
27
|
-
metadata_checksum_type: root.checksum_type,
|
28
|
-
package_checksum_type: root.checksum_type
|
29
|
-
}
|
30
|
-
)
|
25
|
+
options.merge(checksum_type: root.checksum_type)
|
31
26
|
end
|
32
27
|
|
33
28
|
def specific_create_options
|
@@ -1,3 +1,5 @@
|
|
1
|
-
|
1
|
+
<% content_for(:javascripts) do %>
|
2
|
+
<%= webpacked_plugins_js_for :katello %>
|
3
|
+
<% end %>
|
2
4
|
<% @smartProxyId= @smart_proxy.id %>
|
3
5
|
<%= react_component('Content', smartProxyId: @smartProxyId, organizationId: Organization.current&.id,) %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddContainerPushPropsToRepo < ActiveRecord::Migration[6.1]
|
2
|
+
def change
|
3
|
+
add_column :katello_root_repositories, :is_container_push, :boolean, default: false
|
4
|
+
add_column :katello_root_repositories, :container_push_name, :string, default: nil
|
5
|
+
add_column :katello_root_repositories, :container_push_name_format, :string, default: nil
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class RemoveSha1RepositoryChecksumType < ActiveRecord::Migration[6.1]
|
2
|
+
def up
|
3
|
+
::Katello::Repository.where(saved_checksum_type: 'sha1').update(saved_checksum_type: nil)
|
4
|
+
::Katello::RootRepository.where(checksum_type: 'sha1').update(checksum_type: nil)
|
5
|
+
end
|
6
|
+
|
7
|
+
def down
|
8
|
+
fail ActiveRecord::IrreversibleMigration
|
9
|
+
end
|
10
|
+
end
|
@@ -10,7 +10,12 @@
|
|
10
10
|
angular.module('Bastion.repositories').service('Checksum',
|
11
11
|
['translate', function (translate) {
|
12
12
|
|
13
|
-
this.checksums = [
|
13
|
+
this.checksums = [
|
14
|
+
{name: translate('Default'), id: null},
|
15
|
+
{id: 'sha256', name: 'sha256'},
|
16
|
+
{id: 'sha384', name: 'sha384'},
|
17
|
+
{id: 'sha512', name: 'sha512'}
|
18
|
+
];
|
14
19
|
|
15
20
|
this.checksumType = function (checksum) {
|
16
21
|
if (checksum === null) {
|
@@ -371,9 +371,6 @@
|
|
371
371
|
ng-model="repository.checksum_type"
|
372
372
|
ng-options="type.id as type.name for type in checksums">
|
373
373
|
</select>
|
374
|
-
<p class="help-block" translate>
|
375
|
-
For older operating systems such as Red Hat Enterprise Linux 5 or CentOS 5 it is recommended to use sha1.
|
376
|
-
</p>
|
377
374
|
</div>
|
378
375
|
|
379
376
|
<div class="checkbox" ng-hide="repository.content_type === 'docker' || repository.content_type === 'ansible_collection'">
|
data/lib/katello/plugin.rb
CHANGED
@@ -634,6 +634,12 @@ Foreman::Plugin.register :katello do
|
|
634
634
|
full_name: N_('Orphaned Content Protection Time'),
|
635
635
|
description: N_('Time in minutes before content that is not contained within a repository and has not been accessed is considered orphaned.')
|
636
636
|
|
637
|
+
setting 'completed_pulp_task_protection_days',
|
638
|
+
type: :integer,
|
639
|
+
default: 30,
|
640
|
+
full_name: N_('Completed pulp task protection days'),
|
641
|
+
description: N_('How many days before a completed Pulp task is purged by Orphan Cleanup.')
|
642
|
+
|
637
643
|
setting 'remote_execution_prefer_registered_through_proxy',
|
638
644
|
type: :boolean,
|
639
645
|
default: false,
|
@@ -651,6 +657,12 @@ Foreman::Plugin.register :katello do
|
|
651
657
|
default: true,
|
652
658
|
full_name: N_('Distribute archived content view versions'),
|
653
659
|
description: N_("If this is enabled, repositories of content view versions without environments (\"archived\") will be distributed at '/pulp/content/<organization>/content_views/<content view>/X.Y/...'.")
|
660
|
+
|
661
|
+
setting 'automatic_content_count_updates',
|
662
|
+
type: :boolean,
|
663
|
+
default: true,
|
664
|
+
full_name: N_('Calculate content counts on smart proxies automatically'),
|
665
|
+
description: N_("If this is enabled, content counts on smart proxies will be updated automatically after content sync.")
|
654
666
|
end
|
655
667
|
end
|
656
668
|
|
data/lib/katello/version.rb
CHANGED
data/package.json
CHANGED
@@ -1,59 +1,53 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
-
import
|
3
|
+
import { useUrlParams } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { orgId } from '../../services/api';
|
6
6
|
import TableSchema from '../ModuleStreams/ModuleStreamsTableSchema';
|
7
7
|
import GenericContentPage from '../../components/Content/GenericContentPage';
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
const ModuleStreamsPage = (props) => {
|
10
|
+
const { searchParam } = useUrlParams();
|
11
|
+
const [searchQuery, setSearchQuery] = useState(searchParam || '');
|
12
|
+
const { getModuleStreams } = props;
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
};
|
17
|
-
}
|
18
|
-
|
19
|
-
componentDidMount() {
|
20
|
-
this.props.getModuleStreams({
|
21
|
-
search: this.state.searchQuery,
|
14
|
+
useEffect(() => {
|
15
|
+
getModuleStreams({
|
16
|
+
search: searchQuery,
|
22
17
|
});
|
23
|
-
}
|
18
|
+
}, [getModuleStreams, searchQuery]);
|
24
19
|
|
25
|
-
onPaginationChange = (pagination) => {
|
26
|
-
|
20
|
+
const onPaginationChange = (pagination) => {
|
21
|
+
props.getModuleStreams({
|
27
22
|
...pagination,
|
28
23
|
});
|
29
24
|
};
|
30
25
|
|
31
|
-
onSearch = (search) => {
|
32
|
-
|
26
|
+
const onSearch = (search) => {
|
27
|
+
props.getModuleStreams({ search });
|
33
28
|
};
|
34
29
|
|
35
|
-
updateSearchQuery = (
|
36
|
-
|
30
|
+
const updateSearchQuery = (newSearchQuery) => {
|
31
|
+
setSearchQuery(newSearchQuery);
|
37
32
|
};
|
38
33
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
}
|
34
|
+
const { moduleStreams } = props;
|
35
|
+
return (
|
36
|
+
<GenericContentPage
|
37
|
+
header={__('Module Streams')}
|
38
|
+
content={moduleStreams}
|
39
|
+
tableSchema={TableSchema}
|
40
|
+
onSearch={onSearch}
|
41
|
+
autocompleteEndpoint="/katello/api/v2/module_streams"
|
42
|
+
autocompleteQueryParams={{ organization_id: orgId() }}
|
43
|
+
bookmarkController="katello_module_streams"
|
44
|
+
updateSearchQuery={updateSearchQuery}
|
45
|
+
initialInputValue={searchQuery}
|
46
|
+
onPaginationChange={onPaginationChange}
|
47
|
+
/>
|
48
|
+
);
|
49
|
+
};
|
50
|
+
|
57
51
|
|
58
52
|
ModuleStreamsPage.propTypes = {
|
59
53
|
location: PropTypes.shape({
|
@@ -1,19 +1,21 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import { shallow } from 'enzyme';
|
3
3
|
import toJson from 'enzyme-to-json';
|
4
|
+
import * as hooks from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
|
4
5
|
import ModuleStreamsPage from '../ModuleStreamsPage';
|
5
6
|
import GenericContentPage from '../../../components/Content/GenericContentPage';
|
6
7
|
|
7
8
|
describe('Module streams page', () => {
|
8
9
|
it('should render and contain appropiate components', async () => {
|
9
10
|
const moduleStreams = {};
|
10
|
-
|
11
|
+
jest.spyOn(hooks, 'useUrlParams').mockImplementation(() => ({
|
12
|
+
searchParam: '',
|
13
|
+
}));
|
11
14
|
const getModuleStreams = () => {};
|
12
15
|
|
13
16
|
const wrapper = shallow(<ModuleStreamsPage
|
14
17
|
moduleStreams={moduleStreams}
|
15
18
|
getModuleStreams={getModuleStreams}
|
16
|
-
location={mockLocation}
|
17
19
|
/>);
|
18
20
|
|
19
21
|
expect(toJson(wrapper)).toMatchSnapshot();
|
@@ -137,6 +137,8 @@ class ManageManifestModal extends Component {
|
|
137
137
|
return name;
|
138
138
|
};
|
139
139
|
|
140
|
+
const manifestExpiredMessage = manifestExpirationDate ? __('Your manifest expired on {expirationDate}. To continue using Red Hat content, import a new manifest.') : __('Your manifest has expired. To continue using Red Hat content, import a new manifest.');
|
141
|
+
|
140
142
|
return (
|
141
143
|
<ForemanModal id={MANAGE_MANIFEST_MODAL_ID} title={__('Manage Manifest')}>
|
142
144
|
<Tabs id="manifest-history-tabs">
|
@@ -181,7 +183,7 @@ class ManageManifestModal extends Component {
|
|
181
183
|
title={__('Manifest expired')}
|
182
184
|
>
|
183
185
|
<FormattedMessage
|
184
|
-
defaultMessage={
|
186
|
+
defaultMessage={manifestExpiredMessage}
|
185
187
|
values={{
|
186
188
|
expirationDate: new Date(manifestExpirationDate).toDateString(),
|
187
189
|
}}
|
@@ -198,7 +200,7 @@ class ManageManifestModal extends Component {
|
|
198
200
|
{getManifestName()}
|
199
201
|
</Col>
|
200
202
|
</Row>
|
201
|
-
{isManifestImported && manifestExpirationDate &&
|
203
|
+
{isManifestImported && Boolean(manifestExpirationDate) &&
|
202
204
|
<Row>
|
203
205
|
<Col sm={5} />
|
204
206
|
<Col sm={7}>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katello
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.13.0
|
4
|
+
version: 4.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- N/A
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -965,6 +965,7 @@ files:
|
|
965
965
|
- app/lib/actions/pulp3/orphan_cleanup/delete_orphan_remotes.rb
|
966
966
|
- app/lib/actions/pulp3/orphan_cleanup/delete_orphan_repository_versions.rb
|
967
967
|
- app/lib/actions/pulp3/orphan_cleanup/delete_orphaned_migrated_repositories.rb
|
968
|
+
- app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb
|
968
969
|
- app/lib/actions/pulp3/orphan_cleanup/remove_orphans.rb
|
969
970
|
- app/lib/actions/pulp3/orphan_cleanup/remove_unneeded_repos.rb
|
970
971
|
- app/lib/actions/pulp3/repository/commit_upload.rb
|
@@ -2058,6 +2059,8 @@ files:
|
|
2058
2059
|
- db/migrate/20240207191223_remove_entitlement_mode_host_statuses.rb
|
2059
2060
|
- db/migrate/20240423112842_add_fields_to_katello_docker_manifest.rb
|
2060
2061
|
- db/migrate/20240502192021_change_katello_repository_rpms_id_seq_to_big_int.rb
|
2062
|
+
- db/migrate/20240520142245_add_container_push_props_to_repo.rb
|
2063
|
+
- db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb
|
2061
2064
|
- db/seeds.d/101-locations.rb
|
2062
2065
|
- db/seeds.d/102-organizations.rb
|
2063
2066
|
- db/seeds.d/104-proxy.rb
|
@@ -5392,13 +5395,10 @@ files:
|
|
5392
5395
|
- webpack/test-utils/nockWrapper.js
|
5393
5396
|
- webpack/test-utils/react-testing-lib-wrapper.js
|
5394
5397
|
- webpack/test_setup.js
|
5395
|
-
- webpack/utils/__tests__/useParamsWithHash.test.js
|
5396
5398
|
- webpack/utils/helpers.js
|
5397
|
-
- webpack/utils/paramsFromHash.js
|
5398
5399
|
- webpack/utils/useDebounce.js
|
5399
5400
|
- webpack/utils/useEventListener.js
|
5400
5401
|
- webpack/utils/useKatelloDocUrl.js
|
5401
|
-
- webpack/utils/useUrlParams.js
|
5402
5402
|
homepage: http://www.katello.org
|
5403
5403
|
licenses:
|
5404
5404
|
- GPL-2.0
|
@@ -5417,9 +5417,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
5417
5417
|
version: '4'
|
5418
5418
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
5419
5419
|
requirements:
|
5420
|
-
- - "
|
5420
|
+
- - ">="
|
5421
5421
|
- !ruby/object:Gem::Version
|
5422
|
-
version:
|
5422
|
+
version: '0'
|
5423
5423
|
requirements: []
|
5424
5424
|
rubygems_version: 3.4.19
|
5425
5425
|
signing_key:
|
@@ -1,22 +0,0 @@
|
|
1
|
-
import paramsFromHash from '../paramsFromHash';
|
2
|
-
|
3
|
-
test('can parse both hash and query params', () => {
|
4
|
-
const { hash, params: { foo } } = paramsFromHash('#filters?foo=bar');
|
5
|
-
expect(hash).toBe('filters');
|
6
|
-
expect(foo).toBe('bar');
|
7
|
-
});
|
8
|
-
|
9
|
-
test('can parse just hash', () => {
|
10
|
-
const { hash } = paramsFromHash('#filters');
|
11
|
-
expect(hash).toBe('filters');
|
12
|
-
});
|
13
|
-
|
14
|
-
test('can parse just query params', () => {
|
15
|
-
const { params: { foo } } = paramsFromHash('?foo=bar');
|
16
|
-
expect(foo).toBe('bar');
|
17
|
-
});
|
18
|
-
|
19
|
-
test("won't error with blank string", () => {
|
20
|
-
const { hash } = paramsFromHash('');
|
21
|
-
expect(hash).toBe('');
|
22
|
-
});
|
@@ -1,16 +0,0 @@
|
|
1
|
-
import qs from 'query-string';
|
2
|
-
|
3
|
-
/*
|
4
|
-
For when you have a hash with your params in the URL
|
5
|
-
pass in the the 'hash' object from react-router's useLocation();
|
6
|
-
e.g. "mysite.com/foo#bar?baz=bop"
|
7
|
-
will return { hash: 'bar', params: { baz: "bop" }}
|
8
|
-
*/
|
9
|
-
const paramsFromHash = (hash) => {
|
10
|
-
const [baseHash, queryParams = {}] = hash.split('?');
|
11
|
-
const params = qs.parse(queryParams);
|
12
|
-
const trimmedHash = baseHash.replace('#', '').replace('/', '');
|
13
|
-
return { hash: trimmedHash, params };
|
14
|
-
};
|
15
|
-
|
16
|
-
export default paramsFromHash;
|
@@ -1,14 +0,0 @@
|
|
1
|
-
import qs from 'query-string';
|
2
|
-
import { useLocation } from 'react-router-dom';
|
3
|
-
|
4
|
-
// Allows hash and params e.g. "/foo#bar?query=baz"
|
5
|
-
// returns { hash: "bar", params: { query: "baz" } }
|
6
|
-
const useUrlParamsWithHash = () => {
|
7
|
-
const { hash: fullParams } = useLocation();
|
8
|
-
const [hash, queryParams = {}] = fullParams.split('?');
|
9
|
-
const params = qs.parse(queryParams);
|
10
|
-
const trimmedhash = hash.replace('#', '');
|
11
|
-
return { hash: trimmedhash, params };
|
12
|
-
};
|
13
|
-
|
14
|
-
export default useUrlParamsWithHash;
|