/
repositories_controller.rb
675 lines (597 loc) · 37 KB
/
repositories_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
module Katello
class Api::V2::RepositoriesController < Api::V2::ApiController # rubocop:disable Metrics/ClassLength
include Katello::Concerns::FilteredAutoCompleteSearch
generic_repo_wrap_params = []
RepositoryTypeManager.generic_remote_options(defined_only: true).each do |option|
generic_repo_wrap_params << option.name
end
repo_wrap_params = RootRepository.attribute_names.concat([:ignore_global_proxy, :mirror_on_sync]) + generic_repo_wrap_params
wrap_parameters :repository, :include => repo_wrap_params
CONTENT_CREDENTIAL_GPG_KEY_TYPE = "gpg_key".freeze
CONTENT_CREDENTIAL_SSL_CA_CERT_TYPE = "ssl_ca_cert".freeze
CONTENT_CREDENTIAL_SSL_CLIENT_CERT_TYPE = "ssl_client_cert".freeze
CONTENT_CREDENTIAL_SSL_CLIENT_KEY_TYPE = "ssl_client_key".freeze
before_action :find_optional_organization, :only => [:index, :auto_complete_search]
before_action :find_product, :only => [:index, :auto_complete_search]
before_action :find_product_for_create, :only => [:create]
before_action :find_organization_from_product, :only => [:create]
before_action :find_unauthorized_katello_resource, :only => [:gpg_key_content]
before_action :find_authorized_katello_resource, :only => [:show, :update, :destroy, :sync,
:remove_content, :upload_content, :republish,
:import_uploads, :verify_checksum, :reclaim_space]
before_action :find_content, :only => :remove_content
before_action :find_organization_from_repo, :only => [:update]
before_action :error_on_rh_product, :only => [:create]
before_action :check_import_parameters, :only => [:import_uploads]
before_action(:only => [:create, :update]) { find_content_credential CONTENT_CREDENTIAL_GPG_KEY_TYPE }
before_action(:only => [:create, :update]) { find_content_credential CONTENT_CREDENTIAL_SSL_CA_CERT_TYPE }
before_action(:only => [:create, :update]) { find_content_credential CONTENT_CREDENTIAL_SSL_CLIENT_CERT_TYPE }
before_action(:only => [:create, :update]) { find_content_credential CONTENT_CREDENTIAL_SSL_CLIENT_KEY_TYPE }
skip_before_action :authorize, :only => [:gpg_key_content]
skip_before_action :check_media_type, :only => [:upload_content]
def custom_index_relation(collection)
collection.includes(:product)
end
def_param_group :repo do
param :url, String, :desc => N_("repository source url")
param :os_versions, Array,
:desc => N_("Identifies whether the repository should be disabled on a client with a non-matching OS version. Pass [] to enable regardless of OS version. Maximum length 1; allowed tags are: %s") % Katello::RootRepository::ALLOWED_OS_VERSIONS.join(', ')
param :gpg_key_id, :number, :desc => N_("id of the gpg key that will be assigned to the new repository")
param :ssl_ca_cert_id, :number, :desc => N_("Identifier of the content credential containing the SSL CA Cert")
param :ssl_client_cert_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Cert")
param :ssl_client_key_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Key")
param :unprotected, :bool, :desc => N_("true if this repository can be published via HTTP")
param :checksum_type, String, :desc => N_("Checksum of the repository, currently 'sha1' & 'sha256' are supported")
param :docker_upstream_name, String, :desc => N_("Name of the upstream docker repository")
param :docker_tags_whitelist, Array, :desc => N_("Comma-separated list of tags to sync for Container Image repository")
param :download_policy, ["immediate", "on_demand"], :desc => N_("download policy for yum repos (either 'immediate' or 'on_demand')")
param :download_concurrency, :number, :desc => N_("Used to determine download concurrency of the repository in pulp3. Use value less than 20. Defaults to 10")
param :mirror_on_sync, :bool, :desc => N_("true if this repository when synced has to be mirrored from the source and stale rpms removed (Deprecated)")
param :mirroring_policy, Katello::RootRepository::MIRRORING_POLICIES, :desc => N_("Policy to set for mirroring content. Must be one of %s.") % RootRepository::MIRRORING_POLICIES
param :verify_ssl_on_sync, :bool, :desc => N_("if true, Katello will verify the upstream url's SSL certifcates are signed by a trusted CA")
param :upstream_username, String, :desc => N_("Username of the upstream repository user used for authentication")
param :upstream_password, String, :desc => N_("Password of the upstream repository user used for authentication")
param :upstream_authentication_token, String, :desc => N_("Password of the upstream authentication token.")
param :deb_releases, String, :desc => N_("whitespace-separated list of releases to be synced from deb-archive")
param :deb_components, String, :desc => N_("whitespace-separated list of repo components to be synced from deb-archive")
param :deb_architectures, String, :desc => N_("whitespace-separated list of architectures to be synced from deb-archive")
param :ignorable_content, Array, :desc => N_("List of content units to ignore while syncing a yum repository. Must be subset of %s") % RootRepository::IGNORABLE_CONTENT_UNIT_TYPES.join(",")
param :ansible_collection_requirements, String, :desc => N_("Contents of requirement yaml file to sync from URL")
param :ansible_collection_auth_url, String, :desc => N_("The URL to receive a session token from, e.g. used with Automation Hub.")
param :ansible_collection_auth_token, String, :desc => N_("The token key to use for authentication.")
param :http_proxy_policy, ::Katello::RootRepository::HTTP_PROXY_POLICIES, :desc => N_("policies for HTTP proxy for content sync")
param :http_proxy_id, :number, :desc => N_("ID of a HTTP Proxy")
param :arch, String, :desc => N_("Architecture of content in the repository")
param :retain_package_versions_count, :number, :desc => N_("The maximum number of versions of each package to keep.")
RepositoryTypeManager.generic_remote_options(defined_only: true).each do |option|
param option.name, option.type, :desc => N_(option.description)
end
end
def_param_group :repo_create do
param :label, String, :required => false
param :product_id, :number, :required => true, :desc => N_("Product the repository belongs to")
param :content_type, RepositoryTypeManager.creatable_repository_types(false).keys, :required => true, :desc => N_("type of repo")
end
api :GET, "/repositories", N_("List of enabled repositories")
api :GET, "/content_views/:id/repositories", N_("List of repositories for a content view")
api :GET, "/organizations/:organization_id/repositories", N_("List of repositories in an organization")
api :GET, "/organizations/:organization_id/environments/:environment_id/repositories", _("List repositories in the environment")
api :GET, "/products/:product_id/repositories", N_("List of repositories for a product")
api :GET, "/environments/:environment_id/products/:product_id/repositories", N_("List of repositories belonging to a product in an environment")
param :organization_id, :number, :desc => N_("ID of an organization to show repositories in")
param :product_id, :number, :desc => N_("ID of a product to show repositories of")
param :environment_id, :number, :desc => N_("ID of an environment to show repositories in")
param :content_view_id, :number, :desc => N_("ID of a content view to show repositories in")
param :content_view_version_id, :number, :desc => N_("ID of a content view version to show repositories in")
param :deb_id, String, :desc => N_("Id of a deb package to find repositories that contain the deb")
param :erratum_id, String, :desc => N_("Id of an erratum to find repositories that contain the erratum")
param :rpm_id, String, :desc => N_("Id of a rpm package to find repositories that contain the rpm")
param :file_id, String, :desc => N_("Id of a file to find repositories that contain the file")
param :ansible_collection_id, String, :desc => N_("Id of an ansible collection to find repositories that contain the ansible collection")
param :library, :bool, :desc => N_("show repositories in Library and the default content view")
param :archived, :bool, :desc => N_("show archived repositories")
param :content_type, RepositoryTypeManager.defined_repository_types.keys, :desc => N_("limit to only repositories of this type")
param :name, String, :desc => N_("name of the repository"), :required => false
param :label, String, :desc => N_("label of the repository"), :required => false
param :description, String, :desc => N_("description of the repository")
param :available_for, String, :desc => N_("interpret specified object to return only Repositories that can be associated with specified object. Only 'content_view' & 'content_view_version' are supported."),
:required => false
param :with_content, RepositoryTypeManager.enabled_content_types(false), :desc => N_("only repositories having at least one of the specified content type ex: rpm , erratum")
param :download_policy, ::Katello::RootRepository::DOWNLOAD_POLICIES, :desc => N_("limit to only repositories with this download policy")
param :username, String, :desc => N_("only show the repositories readable by this user with this username")
param_group :search, Api::V2::ApiController
add_scoped_search_description_for(Repository)
def index
base_args = [index_relation.distinct, :name, :asc]
options = {:includes => [:environment, {:root => [:gpg_key, :product]}]}
respond_to do |format|
format.csv do
options[:csv] = true
repos = scoped_search(*base_args, options)
csv_response(repos,
[:id, :name, :description, :label, :content_type, :arch, :url, :major, :minor,
:content_label, :pulp_id, :container_repository_name,
:download_policy, 'relative_path', 'product.id', 'product.name',
'environment_id'],
['Id', 'Name', 'Description', 'label', 'Content Type', 'Arch', 'Url', 'Major', 'Minor',
'Content Label', 'Pulp Id', 'Container Repository Name', 'Download Policy', 'Relative Path',
'Product Id', 'Product Name',
'Environment Id'])
end
format.any do
repos = scoped_search(*base_args, options)
respond(:collection => repos)
end
end
end
def index_relation
query = Repository.readable
query = query.with_content(params[:with_content]) if params[:with_content]
query = index_relation_product(query)
query = query.with_type(params[:content_type]) if params[:content_type]
query = query.where(:root_id => RootRepository.where(:name => params[:name])) if params[:name]
query = query.where(:root_id => RootRepository.where(:label => params[:label])) if params[:label]
query = index_relation_content_unit(query)
query = index_relation_content_view(query)
query = index_relation_environment(query)
query
end
def index_relation_product(query)
query = query.joins(:root => :product).where("#{Product.table_name}.organization_id" => @organization) if @organization
query = query.joins(:root).where("#{RootRepository.table_name}.product_id" => @product.id) if @product
query = query.joins(:root).where("#{RootRepository.table_name}.download_policy" => params[:download_policy]) if params[:download_policy]
query
end
def index_relation_content_view(query)
if params[:content_view_version_id]
query = query.where(:content_view_version_id => params[:content_view_version_id])
query = query.archived if ::Foreman::Cast.to_bool params[:archived]
query = Katello::Repository.where(:id => query.select(:library_instance_id)) if params[:library]
elsif params[:content_view_id]
query = filter_by_content_view(query, params[:content_view_id], params[:environment_id], params[:available_for] == 'content_view')
end
query
end
def index_relation_environment(query)
if params[:environment_id] && !params[:library]
query = query.where(:environment_id => params[:environment_id])
elsif params[:environment_id] && params[:library]
instances = query.where(:environment_id => params[:environment_id])
instance_ids = instances.pluck(:library_instance_id).reject(&:blank?)
instance_ids += instances.where(:library_instance_id => nil)
query = Repository.where(:id => instance_ids)
elsif (params[:library] && !params[:environment_id]) || (params[:environment_id].blank? && params[:content_view_version_id].blank? && params[:content_view_id].blank?)
if params[:available_for] == 'content_view_version'
query = query.where.not(:content_view_version_id => nil, :environment_id => nil)
elsif @organization
query = query.where(:content_view_version_id => @organization.default_content_view.versions.first.id)
else
query = query.in_default_view
end
end
query
end
def index_relation_content_unit(query)
if params[:deb_id]
query = query.joins(:debs)
.where("#{Deb.table_name}.id" => Deb.with_identifiers(params[:deb_id]))
end
if params[:erratum_id]
query = query.joins(:errata)
.where("#{Erratum.table_name}.id" => Erratum.with_identifiers(params[:erratum_id]))
end
if params[:rpm_id]
query = query.joins(:rpms)
.where("#{Rpm.table_name}.id" => Rpm.with_identifiers(params[:rpm_id]))
end
if params[:file_id]
query = query.joins(:files)
.where("#{FileUnit.table_name}.id" => FileUnit.with_identifiers(params[:file_id]))
end
if params[:ansible_collection_id]
query = query.joins(:ansible_collections)
.where("#{AnsibleCollection.table_name}.id" => AnsibleCollection.with_identifiers(params[:ansible_collection_id]))
end
generic_type_param = RepositoryTypeManager.generic_content_types.find { |type| params["#{type}_id".to_sym] }
if generic_type_param
query = query.joins(:generic_content_units)
.where("#{GenericContentUnit.table_name}.id" => GenericContentUnit.with_identifiers(params["#{generic_type_param}_id".to_sym]))
end
query
end
api :POST, "/repositories", N_("Create a custom repository")
param :name, String, :desc => N_("Name of the repository"), :required => true
param :description, String, :desc => N_("Description of the repository"), :required => false
param_group :repo_create
param_group :repo
def create
repo_params = repository_params
unless RepositoryTypeManager.creatable_by_user?(repo_params[:content_type], false)
msg = _("Invalid params provided - content_type must be one of %s") %
RepositoryTypeManager.creatable_repository_types(false).keys.join(",")
fail HttpErrors::UnprocessableEntity, msg
end
if !repo_params[:url].nil? && URI(repo_params[:url]).userinfo
fail "Do not include the username/password in the URL. Use the username/password settings instead."
end
gpg_key = get_content_credential(repo_params, CONTENT_CREDENTIAL_GPG_KEY_TYPE)
ssl_ca_cert = get_content_credential(repo_params, CONTENT_CREDENTIAL_SSL_CA_CERT_TYPE)
ssl_client_cert = get_content_credential(repo_params, CONTENT_CREDENTIAL_SSL_CLIENT_CERT_TYPE)
ssl_client_key = get_content_credential(repo_params, CONTENT_CREDENTIAL_SSL_CLIENT_KEY_TYPE)
repo_params[:label] = labelize_params(repo_params)
repo_params[:arch] = repo_params[:arch] || 'noarch'
repo_params[:url] = nil if repo_params[:url].blank?
repo_params[:unprotected] = repo_params.key?(:unprotected) ? repo_params[:unprotected] : true
repo_params[:gpg_key] = gpg_key
repo_params[:ssl_ca_cert] = ssl_ca_cert
repo_params[:ssl_client_cert] = ssl_client_cert
repo_params[:ssl_client_key] = ssl_client_key
root = construct_repo_from_params(repo_params)
sync_task(::Actions::Katello::Repository::CreateRoot, root)
@repository = root.reload.library_instance
respond_for_create(:resource => @repository)
end
api :GET, "/repositories/repository_types", N_("Show the available repository types")
param :creatable, :bool, :desc => N_("When set to 'True' repository types that are creatable will be returned")
def repository_types
creatable = ::Foreman::Cast.to_bool(params[:creatable])
repo_types = creatable ? RepositoryTypeManager.creatable_repository_types : RepositoryTypeManager.enabled_repository_types
render :json => repo_types.values
end
api :PUT, "/repositories/:id/republish", N_("Forces a republish of the specified repository, regenerating metadata and symlinks on the filesystem.")
param :id, :number, :desc => N_("Repository identifier"), :required => true
param :force, :bool, :desc => N_("Force metadata regeneration to proceed. Dangerous when repositories use the 'Complete Mirroring' mirroring policy."), :required => true
def republish
unless ::Foreman::Cast.to_bool(params[:force])
fail HttpErrors::BadRequest, _('Metadata republishing must be forced because it is a dangerous operation.')
end
task = async_task(::Actions::Katello::Repository::MetadataGenerate, @repository)
respond_for_async :resource => task
end
api :GET, "/repositories/:id", N_("Show a repository")
param :id, :number, :required => true, :desc => N_("repository ID")
param :organization_id, :number, :desc => N_("Organization ID")
def show
respond_for_show(:resource => @repository)
end
api :POST, "/repositories/:id/sync", N_("Sync a repository")
param :id, :number, :required => true, :desc => N_("repository ID")
param :source_url, String, :desc => N_("temporarily override feed URL for sync"), :required => false
param :incremental, :bool, :desc => N_("perform an incremental import"), :required => false
param :skip_metadata_check, :bool, :desc => N_("Force sync even if no upstream changes are detected. Only used with yum repositories."), :required => false
param :validate_contents, :bool, :desc => N_("Force a sync and validate the checksums of all content. Only used with yum repositories."), :required => false
def sync
sync_options = {
:skip_metadata_check => ::Foreman::Cast.to_bool(params[:skip_metadata_check]),
:validate_contents => ::Foreman::Cast.to_bool(params[:validate_contents]),
:incremental => ::Foreman::Cast.to_bool(params[:incremental]),
:source_url => params[:source_url]
}
if params[:source_url].present? && params[:source_url] !~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
fail HttpErrors::BadRequest, _("source URL is malformed")
end
if params[:source_url].blank? && @repository.url.blank?
fail HttpErrors::BadRequest, _("attempted to sync without a feed URL")
end
task = async_task(::Actions::Katello::Repository::Sync, @repository, sync_options)
respond_for_async :resource => task
rescue Errors::InvalidActionOptionError => e
raise HttpErrors::BadRequest, e.message
end
api :POST, "/repositories/:id/verify_checksum", N_("Verify checksum of repository contents")
param :id, :number, :required => true, :desc => N_("repository ID")
def verify_checksum
task = async_task(::Actions::Katello::Repository::VerifyChecksum, @repository)
respond_for_async :resource => task
rescue Errors::InvalidActionOptionError => e
raise HttpErrors::BadRequest, e.message
end
api :POST, "/repositories/:id/reclaim_space", N_("Reclaim space from an On Demand repository")
param :id, :number, :required => true, :desc => N_("repository ID")
def reclaim_space
task = async_task(::Actions::Pulp3::Repository::ReclaimSpace, @repository)
respond_for_async :resource => task
rescue Errors::InvalidActionOptionError => e
raise HttpErrors::BadRequest, e.message
end
api :PUT, "/repositories/:id", N_("Update a repository")
param :id, :number, :required => true, :desc => N_("repository ID")
param :name, String, :required => false
param :description, String, :desc => N_("description of the repository"), :required => false
param_group :repo
def update
repo_params = repository_params
if !repo_params[:url].nil? && URI(repo_params[:url]).userinfo
fail "Do not include the username/password in the URL. Use the username/password settings instead."
end
if @repository.generic?
generic_remote_options = generic_remote_options_hash(repo_params)
repo_params[:generic_remote_options] = generic_remote_options.to_json
RepositoryTypeManager.generic_remote_options.each do |option|
repo_params&.delete(option.name)
end
end
sync_task(::Actions::Katello::Repository::Update, @repository.root, repo_params)
respond_for_show(:resource => @repository)
end
api :DELETE, "/repositories/:id", N_("Destroy a custom repository")
param :id, :number, :required => true
param :remove_from_content_view_versions, :bool, :required => false, :desc => N_("Force delete the repository by removing it from all content view versions")
def destroy
sync_task(::Actions::Katello::Repository::Destroy, @repository,
remove_from_content_view_versions: ::Foreman::Cast.to_bool(params.fetch(:remove_from_content_view_versions, false)))
respond_for_destroy
end
api :PUT, "/repositories/:id/remove_packages"
api :PUT, "/repositories/:id/remove_docker_manifests"
api :PUT, "/repositories/:id/remove_content"
desc "Remove content from a repository"
param :id, :number, :required => true, :desc => "repository ID"
param 'ids', Array, :required => true, :desc => "Array of content ids to remove"
param :content_type, RepositoryTypeManager.removable_content_types(false).map(&:label), :required => false, :desc => N_("content type ('deb', 'docker_manifest', 'file', 'ostree', 'rpm', 'srpm')")
param 'sync_capsule', :bool, :desc => N_("Whether or not to sync an external capsule after upload. Default: true")
def remove_content
sync_capsule = ::Foreman::Cast.to_bool(params.fetch(:sync_capsule, true))
fail _("No content ids provided") if @content.blank?
respond_for_async :resource => sync_task(::Actions::Katello::Repository::RemoveContent, @repository, @content, content_type: params[:content_type], sync_capsule: sync_capsule)
end
api :POST, "/repositories/:id/upload_content", N_("Upload content into the repository")
param :id, :number, :required => true, :desc => N_("repository ID")
param :content, File, :required => true, :desc => N_("Content files to upload. Can be a single file or array of files.")
param :content_type, RepositoryTypeManager.uploadable_content_types(false).map(&:label), :required => false, :desc => N_("content type ('deb', 'docker_manifest', 'file', 'ostree', 'rpm', 'srpm')")
def upload_content
fail Katello::Errors::InvalidRepositoryContent, _("Cannot upload Container Image content.") if @repository.docker?
fail Katello::Errors::InvalidRepositoryContent, _("Cannot upload Ansible collections.") if @repository.ansible_collection?
filepaths = Array.wrap(params[:content]).compact.collect do |content|
{path: content.path, filename: content.original_filename}
end
if !filepaths.blank?
sync_task(::Actions::Katello::Repository::UploadFiles, @repository, filepaths, params[:content_type])
render :json => {:status => "success", :filenames => filepaths.map { |item| item[:filename] }}
else
fail HttpErrors::BadRequest, _("No file uploaded")
end
rescue Katello::Errors::InvalidRepositoryContent => error
respond_for_exception(
error,
:status => :unprocessable_entity,
:text => error.message,
:errors => [error.message],
:with_logging => true
)
end
api :PUT, "/repositories/:id/import_uploads", N_("Import uploads into a repository")
param :id, :number, :required => true, :desc => N_("Repository id")
param :async, :bool, desc: N_("Do not wait for the ImportUpload action to finish. Default: false")
param 'publish_repository', :bool, :desc => N_("Whether or not to regenerate the repository on disk. Default: true")
param 'sync_capsule', :bool, :desc => N_("Whether or not to sync an external capsule after upload. Default: true")
param :content_type, RepositoryTypeManager.uploadable_content_types(false).map(&:label), :required => false, :desc => N_("content type ('deb', 'docker_manifest', 'file', 'ostree_ref', 'rpm', 'srpm')")
param :uploads, Array, :desc => N_("Array of uploads to import") do
param 'id', String, :required => true
param 'content_unit_id', String
param 'size', String
param 'checksum', String
param 'name', String, :desc => N_("Needs to only be set for file repositories or docker tags")
param 'digest', String, :desc => N_("Needs to only be set for docker tags")
end
Katello::RepositoryTypeManager.generic_repository_types.each_pair do |_, repo_type|
repo_type.import_attributes.each do |import_attribute|
param import_attribute.api_param, import_attribute.type,
:desc => N_(import_attribute.description)
end
end
def import_uploads
generate_metadata = ::Foreman::Cast.to_bool(params.fetch(:publish_repository, true))
sync_capsule = ::Foreman::Cast.to_bool(params.fetch(:sync_capsule, true))
async = ::Foreman::Cast.to_bool(params.fetch(:async, false))
if params['uploads'].empty?
fail HttpErrors::BadRequest, _('No uploads param specified. An array of uploads to import is required.')
end
uploads = (params[:uploads] || []).map do |upload|
upload.permit(:id, :content_unit_id, :size, :checksum, :name, :digest).to_h
end
begin
upload_args = {
content_type: params[:content_type],
generate_metadata: generate_metadata,
sync_capsule: sync_capsule
}
upload_args.merge!(generic_content_type_import_upload_args)
respond_for_async(resource: send(
async ? :async_task : :sync_task,
::Actions::Katello::Repository::ImportUpload, @repository, uploads, upload_args))
rescue => e
raise HttpErrors::BadRequest, e.message
end
end
# returns the content of a repo gpg key, used directly by yum
# we don't want to authenticate, authorize etc, trying to distinguish between a yum request and normal api request
# might not always be 100% bullet proof, and its more important that yum can fetch the key.
api :GET, "/repositories/:id/gpg_key_content", N_("Return the content of a repo gpg key, used directly by yum")
param :id, :number, :required => true
def gpg_key_content
if @repository.root.gpg_key && @repository.root.gpg_key.content.present?
render(:plain => @repository.root.gpg_key.content, :layout => false)
else
head(404)
end
end
api :GET, "/content_types", N_("Return the enabled content types")
def content_types
render :json => Katello::RepositoryTypeManager.enabled_content_types.map { |type| Katello::RepositoryTypeManager.find_content_type(type) }
end
protected
def find_product
if params[:product_id]
@product = Product.readable.find_by(id: params[:product_id])
throw_resource_not_found(name: 'product', id: params[:product_id]) if @product.nil?
end
find_organization_from_product if @organization.nil? && @product
end
def find_product_for_create
@product = Product.editable.find_by(id: params[:product_id])
throw_resource_not_found(name: 'product', id: params[:product_id]) if @product.nil?
end
def find_content_credential(content_type)
credential_id = "#{content_type}_id".to_sym
credential_var = "@#{content_type}"
if params[credential_id]
credential_value = ContentCredential.readable.where(:id => params[credential_id], :organization_id => @organization).first
instance_variable_set(credential_var, credential_value)
if instance_variable_get(credential_var).nil?
fail HttpErrors::NotFound, _("Couldn't find %{content_type} with id '%{id}'") % { :content_type => content_type, :id => params[credential_id] }
end
end
end
# rubocop:disable Metrics/CyclomaticComplexity
def repository_params
keys = [:download_policy, :mirror_on_sync, :mirroring_policy, :sync_policy, :arch, :verify_ssl_on_sync, :upstream_password,
:upstream_username, :download_concurrency, :upstream_authentication_token,
{:os_versions => []}, :deb_releases, :deb_components, :deb_architectures, :description,
:http_proxy_policy, :http_proxy_id, :retain_package_versions_count, {:ignorable_content => []}
]
keys += [{:docker_tags_whitelist => []}, :docker_upstream_name] if params[:action] == 'create' || @repository&.docker?
keys += [:ansible_collection_requirements, :ansible_collection_auth_url, :ansible_collection_auth_token] if params[:action] == 'create' || @repository&.ansible_collection?
keys += [:label, :content_type] if params[:action] == "create"
if params[:action] == 'create' || @repository&.generic?
RepositoryTypeManager.generic_remote_options.each do |option|
if option.type == Array
keys += [{option.name => []}]
elsif option.type == Hash
keys += [{option.name => {}}]
else
keys += [option.name]
end
end
end
if params[:action] == 'create' || @repository.custom?
keys += [:url, :gpg_key_id, :ssl_ca_cert_id, :ssl_client_cert_id, :ssl_client_key_id, :unprotected, :name,
:checksum_type]
end
to_return = params.require(:repository).permit(*keys).to_h.with_indifferent_access
handle_mirror_on_sync(to_return)
end
def get_content_credential(repo_params, content_type)
credential_value = @product.send(content_type)
unless repo_params["#{content_type}_id".to_sym].blank?
credential_value = instance_variable_get("@#{content_type}")
end
credential_value
end
# rubocop:disable Metrics/PerceivedComplexity
def construct_repo_from_params(repo_params) # rubocop:disable Metrics/AbcSize
root = @product.add_repo(repo_params.slice(:label, :name, :description, :url, :content_type, :arch, :unprotected,
:gpg_key, :ssl_ca_cert, :ssl_client_cert, :ssl_client_key,
:checksum_type, :download_policy, :http_proxy_policy).to_h.with_indifferent_access)
root.docker_upstream_name = repo_params[:docker_upstream_name] if repo_params[:docker_upstream_name]
root.docker_tags_whitelist = repo_params.fetch(:docker_tags_whitelist, []) if root.docker?
root.verify_ssl_on_sync = ::Foreman::Cast.to_bool(repo_params[:verify_ssl_on_sync]) if repo_params.key?(:verify_ssl_on_sync)
root.mirroring_policy = repo_params[:mirroring_policy] || Katello::RootRepository::MIRRORING_POLICY_CONTENT
root.upstream_username = repo_params[:upstream_username] if repo_params.key?(:upstream_username)
root.upstream_password = repo_params[:upstream_password] if repo_params.key?(:upstream_password)
root.upstream_authentication_token = repo_params[:upstream_authentication_token] if repo_params.key?(:upstream_authentication_token)
root.ignorable_content = repo_params[:ignorable_content] if root.yum? && repo_params.key?(:ignorable_content)
root.http_proxy_policy = repo_params[:http_proxy_policy] if repo_params.key?(:http_proxy_policy)
root.http_proxy_id = repo_params[:http_proxy_id] if repo_params.key?(:http_proxy_id)
root.os_versions = repo_params.fetch(:os_versions, []) if root.yum?
root.retain_package_versions_count = repo_params[:retain_package_versions_count] if root.yum? && repo_params.key?(:retain_package_versions_count)
if root.generic?
generic_remote_options = generic_remote_options_hash(repo_params)
root.generic_remote_options = generic_remote_options.to_json
end
if root.deb?
root.deb_releases = repo_params[:deb_releases] if repo_params[:deb_releases]
root.deb_components = repo_params[:deb_components] if repo_params[:deb_components]
root.deb_architectures = repo_params[:deb_architectures] if repo_params[:deb_architectures]
end
if root.ansible_collection?
root.ansible_collection_requirements = repo_params[:ansible_collection_requirements] if repo_params[:ansible_collection_requirements]
root.ansible_collection_auth_url = repo_params[:ansible_collection_auth_url] if repo_params[:ansible_collection_auth_url]
root.ansible_collection_auth_token = repo_params[:ansible_collection_auth_token] if repo_params[:ansible_collection_auth_token]
end
root
end
# rubocop:enable Metrics/CyclomaticComplexity
def handle_mirror_on_sync(repo_params)
if !repo_params.key?(:mirroring_policy) && repo_params.key?(:mirror_on_sync)
::Foreman::Deprecation.api_deprecation_warning("mirror_on_sync is deprecated in favor of mirroring_policy. It will be removed in Katello 4.6.")
if ::Foreman::Cast.to_bool(repo_params[:mirror_on_sync])
repo_params[:mirroring_policy] = Katello::RootRepository::MIRRORING_POLICY_CONTENT
else
repo_params[:mirroring_policy] = Katello::RootRepository::MIRRORING_POLICY_ADDITIVE
end
end
repo_params.delete(:mirror_on_sync)
repo_params
end
def error_on_rh_product
fail HttpErrors::BadRequest, _("Red Hat products cannot be manipulated.") if @product.redhat?
end
def error_on_rh_repo
fail HttpErrors::BadRequest, _("Red Hat repositories cannot be manipulated.") if @repository.redhat?
end
def find_organization_from_repo
@organization = @repository.organization
end
def find_organization_from_product
@organization = @product.organization
end
def find_content
content_type = params[:content_type]
if content_type
RepositoryTypeManager.check_content_matches_repo_type!(@repository, params[:content_type]) if params[:content_type]
@content = @repository.units_for_removal(params[:ids], content_type)
else
@content = @repository.units_for_removal(params[:ids])
end
if @repository.generic?
if content_type
RepositoryTypeManager.check_content_matches_repo_type!(@repository, @content.first.content_type)
else
RepositoryTypeManager.check_content_matches_repo_type!(@repository, @repository.repository_type.default_managed_content_type.label)
end
else
RepositoryTypeManager.check_content_matches_repo_type!(@repository, @content.first.class::CONTENT_TYPE)
end
end
def filter_by_content_view(query, content_view_id, environment_id, is_available_for)
if is_available_for
params[:library] = true
sub_query = ContentViewRepository.where(:content_view_id => content_view_id).pluck(:repository_id)
query = query.where("#{Repository.table_name}.id not in (#{sub_query.join(',')})") unless sub_query.empty?
elsif environment_id
version = ContentViewVersion.in_environment(environment_id).where(:content_view_id => content_view_id)
query = query.where(:content_view_version_id => version)
elsif params[:available_for] != 'content_view_version'
query = query.joins(:content_view_repositories).where("#{ContentViewRepository.table_name}.content_view_id" => content_view_id)
else
version_ids = ContentViewVersion.where(:content_view_id => content_view_id).pluck(:id)
query = query.where('content_view_version_id IN (?) AND environment_id IS NOT NULL', version_ids)
end
query
end
def generic_remote_options_hash(repo_params)
generic_remote_options = {}
RepositoryTypeManager.generic_remote_options(content_type: repo_params[:content_type]).each do |option|
generic_remote_options[option.name] = repo_params[option.name]
end
generic_remote_options
end
def generic_content_type_import_upload_args
args = {}
@repository.repository_type&.import_attributes&.collect do |import_attribute|
if params[import_attribute.api_param]
args[import_attribute.api_param] = params[import_attribute.api_param]
end
end
args
end
def check_import_parameters
@repository.repository_type&.import_attributes&.each do |import_attribute|
if import_attribute.required && params[import_attribute.api_param].blank?
fail HttpErrors::UnprocessableEntity, _("%s is required", import_attributes.api_param)
end
end
end
end
end