katello 4.13.0 → 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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +8 -0
  3. data/app/lib/actions/katello/repository/discover.rb +11 -4
  4. data/app/lib/actions/pulp3/repository/create_publication.rb +4 -0
  5. data/app/lib/katello/repo_discovery.rb +4 -190
  6. data/app/lib/katello/resources/discovery/container.rb +127 -0
  7. data/app/lib/katello/resources/discovery/yum.rb +95 -0
  8. data/app/lib/katello/util/http_helper.rb +15 -0
  9. data/app/models/732bd3db9f64c621c64d2be4f2a838727aac0845.patch +61 -0
  10. data/app/models/katello/repository.rb.bak +978 -0
  11. data/app/models/katello/root_repository.rb +10 -0
  12. data/app/services/katello/pulp3/content_view_version/import_validator.rb.bak +166 -0
  13. data/app/services/katello/pulp3/content_view_version/importable_repositories.rb.bak +164 -0
  14. data/app/services/katello/repository_type.rb +1 -1
  15. data/config/initializers/monkeys.rb +0 -1
  16. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +3 -0
  17. data/lib/katello/repository_types/docker.rb +1 -0
  18. data/lib/katello/repository_types/yum.rb +1 -0
  19. data/lib/katello/tasks/update_repository_expiry.rake +114 -0
  20. data/lib/katello/version.rb +1 -1
  21. data/lib/katello.rb +0 -2
  22. data/locale/bn/katello.po.time_stamp +0 -0
  23. data/locale/bn_IN/katello.po.time_stamp +0 -0
  24. data/locale/ca/katello.po.time_stamp +0 -0
  25. data/locale/cs/katello.po.time_stamp +0 -0
  26. data/locale/cs_CZ/katello.po.time_stamp +0 -0
  27. data/locale/de/katello.po.time_stamp +0 -0
  28. data/locale/de_AT/katello.po.time_stamp +0 -0
  29. data/locale/de_DE/katello.po.time_stamp +0 -0
  30. data/locale/el/katello.po.time_stamp +0 -0
  31. data/locale/en/katello.po.time_stamp +0 -0
  32. data/locale/en_GB/katello.po.time_stamp +0 -0
  33. data/locale/en_US/katello.po.time_stamp +0 -0
  34. data/locale/es/katello.po.time_stamp +0 -0
  35. data/locale/et_EE/katello.po.time_stamp +0 -0
  36. data/locale/fr/katello.po.time_stamp +0 -0
  37. data/locale/gl/katello.po.time_stamp +0 -0
  38. data/locale/gu/katello.po.time_stamp +0 -0
  39. data/locale/he_IL/katello.po.time_stamp +0 -0
  40. data/locale/hi/katello.po.time_stamp +0 -0
  41. data/locale/id/katello.po.time_stamp +0 -0
  42. data/locale/it/katello.po.time_stamp +0 -0
  43. data/locale/ja/katello.po.time_stamp +0 -0
  44. data/locale/ka/katello.po.time_stamp +0 -0
  45. data/locale/kn/katello.po.time_stamp +0 -0
  46. data/locale/ko/katello.po.time_stamp +0 -0
  47. data/locale/ml_IN/katello.po.time_stamp +0 -0
  48. data/locale/mr/katello.po.time_stamp +0 -0
  49. data/locale/nl_NL/katello.po.time_stamp +0 -0
  50. data/locale/or/katello.po.time_stamp +0 -0
  51. data/locale/pa/katello.po.time_stamp +0 -0
  52. data/locale/pl/katello.po.time_stamp +0 -0
  53. data/locale/pl_PL/katello.po.time_stamp +0 -0
  54. data/locale/pt/katello.po.time_stamp +0 -0
  55. data/locale/pt_BR/katello.po.time_stamp +0 -0
  56. data/locale/ro/katello.po.time_stamp +0 -0
  57. data/locale/ro_RO/katello.po.time_stamp +0 -0
  58. data/locale/ru/katello.po.time_stamp +0 -0
  59. data/locale/sl/katello.po.time_stamp +0 -0
  60. data/locale/sv_SE/katello.po.time_stamp +0 -0
  61. data/locale/ta/katello.po.time_stamp +0 -0
  62. data/locale/ta_IN/katello.po.time_stamp +0 -0
  63. data/locale/te/katello.po.time_stamp +0 -0
  64. data/locale/tr/katello.po.time_stamp +0 -0
  65. data/locale/vi/katello.po.time_stamp +0 -0
  66. data/locale/vi_VN/katello.po.time_stamp +0 -0
  67. data/locale/zh/katello.po.time_stamp +0 -0
  68. data/locale/zh_CN/katello.po.time_stamp +0 -0
  69. data/locale/zh_TW/katello.po.time_stamp +0 -0
  70. data/webpack/global_test_setup.js.bak +59 -0
  71. metadata +83 -24
  72. data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +0 -10
  73. data/lib/monkeys/anemone.rb +0 -33
@@ -0,0 +1,978 @@
1
+ module Katello
2
+ # rubocop:disable Metrics/ClassLength
3
+ class Repository < Katello::Model
4
+ audited
5
+
6
+ #pulp uses pulp id to sync with 'yum_distributor' on the end
7
+ PULP_ID_MAX_LENGTH = 220
8
+
9
+ validates_lengths_from_database
10
+ before_destroy :assert_deletable
11
+ before_create :downcase_pulp_id
12
+
13
+ include ForemanTasks::Concerns::ActionSubject
14
+ include Glue::Candlepin::Repository
15
+ include Glue::Pulp::Repo
16
+
17
+ include Glue
18
+ include Authorization::Repository
19
+ include Katello::Engine.routes.url_helpers
20
+
21
+ include ERB::Util
22
+ include ::ScopedSearchExtensions
23
+
24
+ AUDIT_SYNC_ACTION = 'sync'.freeze
25
+
26
+ DEB_TYPE = 'deb'.freeze
27
+ YUM_TYPE = 'yum'.freeze
28
+ FILE_TYPE = 'file'.freeze
29
+ DOCKER_TYPE = 'docker'.freeze
30
+ OSTREE_TYPE = 'ostree'.freeze
31
+ ANSIBLE_COLLECTION_TYPE = 'ansible_collection'.freeze
32
+ GENERIC_TYPE = 'generic'.freeze
33
+
34
+ EXPORTABLE_TYPES = [YUM_TYPE, FILE_TYPE, ANSIBLE_COLLECTION_TYPE].freeze
35
+
36
+ define_model_callbacks :sync, :only => :after
37
+
38
+ belongs_to :root, :inverse_of => :repositories, :class_name => "Katello::RootRepository"
39
+ belongs_to :environment, :inverse_of => :repositories, :class_name => "Katello::KTEnvironment"
40
+ belongs_to :library_instance, :class_name => "Katello::Repository", :inverse_of => :library_instances_inverse
41
+ has_many :library_instances_inverse,
42
+ :class_name => 'Katello::Repository',
43
+ :dependent => :restrict_with_exception,
44
+ :foreign_key => :library_instance_id
45
+
46
+ has_one :product, :through => :root
47
+
48
+ has_many :content_view_repositories, :class_name => "Katello::ContentViewRepository",
49
+ :dependent => :destroy, :inverse_of => :repository
50
+ has_many :content_views, :through => :content_view_repositories
51
+
52
+ has_many :repository_errata, :class_name => "Katello::RepositoryErratum", :dependent => :delete_all
53
+ has_many :errata, :through => :repository_errata
54
+
55
+ has_many :repository_rpms, :class_name => "Katello::RepositoryRpm", :dependent => :delete_all
56
+ has_many :rpms, :through => :repository_rpms
57
+
58
+ has_many :repository_srpms, :class_name => "Katello::RepositorySrpm", :dependent => :delete_all
59
+ has_many :srpms, :through => :repository_srpms
60
+
61
+ has_many :repository_generic_content_units, :class_name => "Katello::RepositoryGenericContentUnit", :dependent => :delete_all
62
+ has_many :generic_content_units, :through => :repository_generic_content_units
63
+
64
+ has_many :repository_file_units, :class_name => "Katello::RepositoryFileUnit", :dependent => :delete_all
65
+ has_many :files, :through => :repository_file_units, :source => :file_unit
66
+ alias_attribute :file_units, :files
67
+
68
+ has_many :repository_docker_manifests, :class_name => "Katello::RepositoryDockerManifest", :dependent => :delete_all
69
+ has_many :docker_manifests, :through => :repository_docker_manifests
70
+
71
+ has_many :repository_docker_manifest_lists, :class_name => "Katello::RepositoryDockerManifestList", :dependent => :delete_all
72
+ has_many :docker_manifest_lists, :through => :repository_docker_manifest_lists
73
+
74
+ has_many :yum_metadata_files, :dependent => :destroy, :class_name => "Katello::YumMetadataFile"
75
+
76
+ has_many :repository_docker_tags, :class_name => "Katello::RepositoryDockerTag", :dependent => :delete_all
77
+ has_many :docker_tags, :through => :repository_docker_tags
78
+
79
+ has_many :repository_docker_meta_tags, :class_name => "Katello::RepositoryDockerMetaTag", :dependent => :delete_all
80
+ has_many :docker_meta_tags, :through => :repository_docker_meta_tags
81
+
82
+ has_many :repository_debs, :class_name => "Katello::RepositoryDeb", :dependent => :delete_all
83
+ has_many :debs, :through => :repository_debs
84
+
85
+ has_many :content_facet_repositories, :class_name => "Katello::ContentFacetRepository", :dependent => :delete_all
86
+ has_many :content_facets, :through => :content_facet_repositories
87
+
88
+ has_many :repository_package_groups, :class_name => "Katello::RepositoryPackageGroup", :dependent => :delete_all
89
+ has_many :package_groups, :through => :repository_package_groups
90
+
91
+ has_many :kickstart_content_facets, :class_name => "Katello::Host::ContentFacet", :foreign_key => :kickstart_repository_id,
92
+ :inverse_of => :kickstart_repository, :dependent => :nullify
93
+
94
+ has_many :kickstart_hostgroup_content_facets, :class_name => "Katello::Hostgroup::ContentFacet", :foreign_key => :kickstart_repository_id,
95
+ :inverse_of => :kickstart_repository, :dependent => :nullify
96
+
97
+ has_many :kickstart_hostgroups, :class_name => "::Hostgroup", :through => :kickstart_hostgroup_content_facets
98
+
99
+ has_many :repository_module_streams, class_name: "Katello::RepositoryModuleStream", dependent: :delete_all
100
+ has_many :module_streams, through: :repository_module_streams
101
+
102
+ has_many :repository_ansible_collections, :class_name => "Katello::RepositoryAnsibleCollection", :dependent => :delete_all
103
+ has_many :ansible_collections, :through => :repository_ansible_collections
104
+ has_many :repository_content_view_filters, :class_name => "Katello::RepositoryContentViewFilter", :dependent => :delete_all
105
+ has_many :filters, :through => :repository_content_view_filters
106
+
107
+ belongs_to :content_view_version, :inverse_of => :repositories, :class_name => "Katello::ContentViewVersion"
108
+ has_many :distribution_references, :class_name => 'Katello::Pulp3::DistributionReference',
109
+ :dependent => :destroy, :inverse_of => :repository
110
+
111
+ has_many :smart_proxy_sync_histories, :class_name => "::Katello::SmartProxySyncHistory", :inverse_of => :repository, :dependent => :delete_all
112
+
113
+ has_many :smart_proxy_alternate_content_sources, :class_name => 'Katello::SmartProxyAlternateContentSource', :inverse_of => :repository, :dependent => :nullify
114
+
115
+ validates_with Validators::ContainerImageNameValidator, :attributes => :container_repository_name, :allow_blank => false, :if => :docker?
116
+ validates :container_repository_name, :if => :docker?, :uniqueness => {message: ->(object, _data) do
117
+ _("for repository '%{name}' is not unique and cannot be created in '%{env}'. Its Container Repository Name (%{container_name}) conflicts with an existing repository. Consider changing the Lifecycle Environment's Registry Name Pattern to something more specific.") %
118
+ {name: object.name, container_name: object.container_repository_name, :env => object.environment.name}
119
+ end}
120
+
121
+ before_validation :set_pulp_id
122
+ before_validation :set_container_repository_name, :if => :docker?
123
+
124
+ scope :has_url, -> { joins(:root).where.not("#{RootRepository.table_name}.url" => nil) }
125
+ scope :on_demand, -> { joins(:root).where("#{RootRepository.table_name}.download_policy" => ::Katello::RootRepository::DOWNLOAD_ON_DEMAND) }
126
+ scope :immediate, -> { joins(:root).where("#{RootRepository.table_name}.download_policy" => ::Katello::RootRepository::DOWNLOAD_IMMEDIATE) }
127
+ scope :non_immediate, -> { joins(:root).where.not("#{RootRepository.table_name}.download_policy" => ::Katello::RootRepository::DOWNLOAD_IMMEDIATE) }
128
+ scope :in_default_view, -> { joins(:content_view_version => :content_view).where("#{Katello::ContentView.table_name}.default" => true) }
129
+ scope :in_non_default_view, -> { joins(:content_view_version => :content_view).where("#{Katello::ContentView.table_name}.default" => false) }
130
+ scope :deb_type, -> { with_type(DEB_TYPE) }
131
+ scope :yum_type, -> { with_type(YUM_TYPE) }
132
+ scope :file_type, -> { with_type(FILE_TYPE) }
133
+ scope :docker_type, -> { with_type(DOCKER_TYPE) }
134
+ scope :ostree_type, -> { with_type(OSTREE_TYPE) }
135
+ scope :ansible_collection_type, -> { with_type(ANSIBLE_COLLECTION_TYPE) }
136
+ scope :generic_type, -> { with_type(Katello::RepositoryTypeManager.enabled_repository_types.select { |_, v| v.pulp3_service_class == Katello::Pulp3::Repository::Generic }.keys) }
137
+ scope :non_archived, -> { where('environment_id is not NULL') }
138
+ scope :archived, -> { where('environment_id is NULL') }
139
+ scope :in_published_environments, -> { in_content_views(Katello::ContentView.non_default).where.not(:environment_id => nil) }
140
+ scope :order_by_root, ->(attr) { joins(:root).order("#{Katello::RootRepository.table_name}.#{attr}") }
141
+ scope :with_content, ->(content) { joins(Katello::RepositoryTypeManager.find_content_type(content).model_class.repository_association_class.name.demodulize.underscore.pluralize.to_sym).distinct }
142
+ scope :by_rpm_count, -> { left_joins(:repository_rpms).group(:id).order("count(katello_repository_rpms.id) ASC") } # smallest count first
143
+ scope :exportable, -> { with_type(EXPORTABLE_TYPES) }
144
+ scope :syncable_exportable, -> { with_type([YUM_TYPE, FILE_TYPE]) }
145
+
146
+ scope :immediate_or_none, -> do
147
+ immediate.or(where("#{RootRepository.table_name}.download_policy" => nil)).
148
+ or(where("#{RootRepository.table_name}.download_policy" => ""))
149
+ end
150
+ scope :redhat, -> { joins(:product => :provider).where("#{Provider.table_name}.provider_type": Provider::REDHAT) }
151
+ scope :custom, -> { joins(:product => :provider).where.not("#{Provider.table_name}.provider_type": Provider::REDHAT) }
152
+ scope :library, -> { where(library_instance_id: nil) }
153
+
154
+ scoped_search :on => :name, :relation => :root, :complete_value => true
155
+ scoped_search :rename => :product, :on => :name, :relation => :product, :complete_value => true
156
+ scoped_search :rename => :product_id, :on => :id, :relation => :product
157
+ scoped_search :on => :content_type, :relation => :root, :complete_value => -> do
158
+ Katello::RepositoryTypeManager.enabled_repository_types.keys.index_by { |value| value.to_sym }
159
+ end
160
+ scoped_search :on => :content_view_id, :relation => :content_view_repositories, :validator => ScopedSearch::Validators::INTEGER, :only_explicit => true
161
+ scoped_search :on => :distribution_version, :complete_value => true
162
+ scoped_search :on => :distribution_arch, :complete_value => true
163
+ scoped_search :on => :distribution_family, :complete_value => true
164
+ scoped_search :on => :distribution_variant, :complete_value => true
165
+ scoped_search :on => :distribution_bootable, :complete_value => true
166
+ scoped_search :on => :redhat, :complete_value => { :true => true, :false => false }, :ext_method => :search_by_redhat
167
+ scoped_search :on => :container_repository_name, :complete_value => true
168
+ scoped_search :on => :description, :relation => :root, :only_explicit => true
169
+ scoped_search :on => :download_policy, :relation => :root, :only_explicit => true
170
+ scoped_search :on => :name, :relation => :product, :rename => :product_name
171
+ scoped_search :on => :id, :relation => :product, :rename => :product_id, :only_explicit => true
172
+ scoped_search :on => :label, :relation => :root, :complete_value => true, :only_explicit => true
173
+ scoped_search :on => :content_label, :ext_method => :search_by_content_label
174
+
175
+ delegate :product, :redhat?, :custom?, :to => :root
176
+ delegate :yum?, :docker?, :deb?, :file?, :ostree?, :ansible_collection?, :generic?, :to => :root
177
+ delegate :name, :label, :docker_upstream_name, :url, :download_concurrency, :to => :root
178
+
179
+ delegate :name, :created_at, :updated_at, :major, :minor, :gpg_key_id, :gpg_key, :arch, :label, :url, :unprotected,
180
+ :content_type, :product_id, :checksum_type, :docker_upstream_name, :mirroring_policy,
181
+ :download_policy, :verify_ssl_on_sync, :"verify_ssl_on_sync?", :upstream_username, :upstream_password,
182
+ :upstream_authentication_token, :deb_releases,
183
+ :deb_components, :deb_architectures, :ssl_ca_cert_id, :ssl_ca_cert, :ssl_client_cert, :ssl_client_cert_id,
184
+ :ssl_client_key_id, :os_versions, :ssl_client_key, :ignorable_content, :description, :include_tags, :exclude_tags,
185
+ :docker_tags_whitelist, :ansible_collection_requirements, :ansible_collection_auth_url, :ansible_collection_auth_token,
186
+ :http_proxy_policy, :http_proxy_id, :to => :root
187
+
188
+ delegate :content_id, to: :root, allow_nil: true
189
+ delegate :repository_type, to: :root
190
+
191
+ def self.with_type(content_type)
192
+ joins(:root).where("#{RootRepository.table_name}.content_type" => content_type)
193
+ end
194
+
195
+ def self.for_products(products)
196
+ joins(:root).where("#{Katello::RootRepository.table_name}.product_id" => products)
197
+ end
198
+
199
+ def to_label
200
+ name
201
+ end
202
+
203
+ def backend_service(smart_proxy, force_pulp3 = false)
204
+ if force_pulp3 || smart_proxy.pulp3_support?(self)
205
+ @service ||= Katello::Pulp3::Repository.instance_for_type(self, smart_proxy)
206
+ else
207
+ @service ||= Katello::Pulp::Repository.instance_for_type(self, smart_proxy)
208
+ end
209
+ end
210
+
211
+ def backend_content_service(smart_proxy)
212
+ backend_service(smart_proxy).content_service
213
+ end
214
+
215
+ def backend_content_unit_service(smart_proxy, content_unit_type)
216
+ backend_service(smart_proxy).content_service(content_unit_type)
217
+ end
218
+
219
+ def organization
220
+ if self.environment
221
+ self.environment.organization
222
+ else
223
+ self.content_view.organization
224
+ end
225
+ end
226
+
227
+ def organization_id
228
+ organization&.id
229
+ end
230
+
231
+ def audit_sync
232
+ write_audit(action: AUDIT_SYNC_ACTION, comment: _('Successfully synchronized.'), audited_changes: {})
233
+ end
234
+
235
+ def set_pulp_id
236
+ return if self.pulp_id
237
+
238
+ if self.content_view.default?
239
+ items = [SecureRandom.uuid]
240
+ elsif self.environment
241
+ items = [organization.id, content_view.label, environment.label, library_instance.pulp_id]
242
+ else
243
+ version = self.content_view_version.version.gsub('.', '_')
244
+ items = [organization.id, content_view.label, "v#{version}", library_instance.pulp_id]
245
+ end
246
+ self.pulp_id = items.join('-')
247
+ self.pulp_id = SecureRandom.uuid if self.pulp_id.length > PULP_ID_MAX_LENGTH
248
+ end
249
+
250
+ def set_container_repository_name
251
+ self.container_repository_name = Repository.safe_render_container_name(self)
252
+ end
253
+
254
+ def content_view
255
+ self.content_view_version.content_view
256
+ end
257
+
258
+ def library_instance?
259
+ self.content_view.default?
260
+ end
261
+
262
+ def self.undisplayable_types
263
+ [::Katello::Repository::CANDLEPIN_DOCKER_TYPE]
264
+ end
265
+
266
+ def self.in_organization(org)
267
+ where("#{Repository.table_name}.environment_id" => org.kt_environments.pluck("#{KTEnvironment.table_name}.id"))
268
+ end
269
+
270
+ def self.in_environment(env_id)
271
+ where(environment_id: env_id)
272
+ end
273
+
274
+ def self.in_product(prod)
275
+ where(:root_id => RootRepository.where(product_id: prod))
276
+ end
277
+
278
+ def self.in_content_views(views)
279
+ joins(:content_view_version)
280
+ .where("#{Katello::ContentViewVersion.table_name}.content_view_id" => views.map(&:id))
281
+ end
282
+
283
+ def self.feed_ca_cert(url)
284
+ file = feed_ca_file(url)
285
+ File.read(file) if file
286
+ end
287
+
288
+ def self.feed_ca_file(url)
289
+ ::Katello::Resources::CDN::CdnResource.ca_file if ::Katello::Resources::CDN::CdnResource.redhat_cdn?(url)
290
+ end
291
+
292
+ def soft_copy_of_library?
293
+ return false if self.version_href.nil?
294
+ self.version_href.starts_with?(self.library_instance.backend_service(SmartProxy.pulp_primary).repository_reference.repository_href)
295
+ end
296
+
297
+ def archive?
298
+ self.environment.nil?
299
+ end
300
+
301
+ def using_mirrored_metadata?
302
+ self.yum? && self.library_instance? && self.root.mirroring_policy == Katello::RootRepository::MIRRORING_POLICY_COMPLETE
303
+ end
304
+
305
+ def in_default_view?
306
+ content_view_version&.default_content_view?
307
+ end
308
+
309
+ def on_demand?
310
+ root.download_policy == ::Katello::RootRepository::DOWNLOAD_ON_DEMAND
311
+ end
312
+
313
+ def immediate?
314
+ root.download_policy == ::Katello::RootRepository::DOWNLOAD_IMMEDIATE
315
+ end
316
+
317
+ def yum_gpg_key_url
318
+ # if the repo has a gpg key return a url to access it
319
+ if self.root.gpg_key.try(:content).present?
320
+ "../..#{gpg_key_content_api_repository_url(self, :only_path => true)}"
321
+ end
322
+ end
323
+
324
+ def product_type
325
+ redhat? ? "redhat" : "custom"
326
+ end
327
+
328
+ def content_counts
329
+ content_counts = {}
330
+ RepositoryTypeManager.defined_repository_types[content_type].content_types_to_index.each do |content_type|
331
+ if content_type&.model_class::CONTENT_TYPE == DockerTag::CONTENT_TYPE
332
+ content_counts[DockerTag::CONTENT_TYPE] = docker_tags.count
333
+ elsif content_type&.model_class::CONTENT_TYPE == GenericContentUnit::CONTENT_TYPE
334
+ content_counts[content_type.content_type] = content_type&.model_class&.in_repositories(self)&.where(:content_type => content_type.content_type)&.count
335
+ else
336
+ content_counts[content_type.label] = content_type&.model_class&.in_repositories(self)&.count
337
+ end
338
+ end
339
+
340
+ content_counts['module_stream'] = content_counts.delete('modulemd') if content_counts.key?('modulemd')
341
+ content_counts
342
+ end
343
+
344
+ def published_in_versions
345
+ Katello::ContentViewVersion.with_repositories(self.library_instances_inverse)
346
+ .where(content_view_id: Katello::ContentView.ignore_generated).distinct
347
+ end
348
+
349
+ def self.errata_with_package_counts(repo)
350
+ repository_rpm = Katello::RepositoryRpm.table_name
351
+ repository_errata = Katello::RepositoryErratum.table_name
352
+ rpm = Katello::Rpm.table_name
353
+ errata = Katello::Erratum.table_name
354
+ erratum_package = Katello::ErratumPackage.table_name
355
+ ::Katello::Erratum.joins(
356
+ "INNER JOIN #{erratum_package} on #{erratum_package}.erratum_id = #{errata}.id",
357
+ "INNER JOIN #{repository_errata} on #{repository_errata}.erratum_id = #{errata}.id",
358
+ "INNER JOIN #{rpm} on #{rpm}.filename = #{erratum_package}.filename",
359
+ "INNER JOIN #{repository_rpm} on #{repository_rpm}.rpm_id = #{rpm}.id").
360
+ where("#{repository_rpm}.repository_id" => repo.id).
361
+ where("#{repository_errata}.repository_id" => repo.id).
362
+ group("#{errata}.id").count
363
+ end
364
+
365
+ def self.errata_with_module_stream_counts(repo)
366
+ repository_errata = Katello::RepositoryErratum.table_name
367
+ errata = Katello::Erratum.table_name
368
+ erratum_package = Katello::ErratumPackage.table_name
369
+ repository_module_stream = Katello::RepositoryModuleStream.table_name
370
+ msep = ::Katello::ModuleStreamErratumPackage.table_name
371
+ ::Katello::Erratum.joins(
372
+ "INNER JOIN #{erratum_package} on #{erratum_package}.erratum_id = #{errata}.id",
373
+ "INNER JOIN #{msep} on #{msep}.erratum_package_id = #{erratum_package}.id",
374
+ "INNER JOIN #{repository_errata} on #{repository_errata}.erratum_id = #{errata}.id",
375
+ "INNER JOIN #{repository_module_stream} on #{repository_module_stream}.module_stream_id = #{msep}.module_stream_id").
376
+ where("#{repository_module_stream}.repository_id" => repo.id).
377
+ where("#{repository_errata}.repository_id" => repo.id).
378
+ group("#{errata}.id").count
379
+ end
380
+
381
+ def fetch_package_errata_to_keep
382
+ errata_counts = ::Katello::Repository.errata_with_package_counts(self)
383
+ if errata_counts.any?
384
+ errata_counts_in_library = ::Katello::Repository.errata_with_package_counts(library_instance)
385
+ errata_counts.keep_if { |id| errata_counts[id] == errata_counts_in_library[id] }
386
+ errata_counts.keys
387
+ else
388
+ []
389
+ end
390
+ end
391
+
392
+ def fetch_module_errata_to_filter
393
+ errata_counts = ::Katello::Repository.errata_with_module_stream_counts(self)
394
+ errata_counts_in_library = ::Katello::Repository.errata_with_module_stream_counts(library_instance)
395
+ if errata_counts_in_library.any?
396
+ errata_counts_in_library.keep_if { |id| errata_counts[id] != errata_counts_in_library[id] }
397
+ errata_counts_in_library.keys
398
+ else
399
+ []
400
+ end
401
+ end
402
+
403
+ def partial_errata
404
+ return [] if library_instance?
405
+
406
+ partial_errata = self.errata
407
+ errata_to_keep = fetch_package_errata_to_keep - fetch_module_errata_to_filter
408
+
409
+ if errata_to_keep.any?
410
+ partial_errata = self.errata.where("#{Katello::Erratum.table_name}.id NOT IN (?)", errata_to_keep)
411
+ end
412
+
413
+ partial_errata
414
+ end
415
+
416
+ def remove_partial_errata!
417
+ found = partial_errata.to_a
418
+ yield(found) if block_given?
419
+ self.repository_errata.where(:erratum_id => found.map(&:id)).delete_all
420
+ found
421
+ end
422
+
423
+ def siblings
424
+ content_view_version.archived_repos.where.not(:id => id)
425
+ end
426
+
427
+ def clones
428
+ self.root.repositories.where.not(:id => library_instance_id || id)
429
+ end
430
+
431
+ def all_instances
432
+ self.root.repositories
433
+ end
434
+
435
+ def group
436
+ all_instances
437
+ end
438
+
439
+ def full_path(smart_proxy = nil, force_http = false)
440
+ pulp_uri = URI.parse(smart_proxy ? smart_proxy.url : ::SmartProxy.pulp_primary.url)
441
+ scheme = force_http ? 'http' : 'https'
442
+ if docker?
443
+ "#{pulp_uri.host.downcase}/#{container_repository_name}"
444
+ elsif ansible_collection?
445
+ "#{scheme}://#{pulp_uri.host.downcase}/pulp_ansible/galaxy/#{relative_path}/api/"
446
+ else
447
+ "#{scheme}://#{pulp_uri.host.downcase}/pulp/content/#{relative_path}/"
448
+ end
449
+ end
450
+
451
+ def to_hash(content_source = nil, force_http = false)
452
+ {id: id, name: label, url: full_path(content_source, force_http)}
453
+ end
454
+
455
+ #is the repo cloned in the specified environment
456
+ def cloned_in?(env)
457
+ !get_clone(env).nil?
458
+ end
459
+
460
+ def promoted?
461
+ if environment&.library?
462
+ self.clones.any?
463
+ else
464
+ false
465
+ end
466
+ end
467
+
468
+ def get_clone(env)
469
+ if self.content_view.default
470
+ # this repo is part of a default content view
471
+ Repository.in_environment(env).clones.
472
+ joins(:content_view_version => :content_view).where("#{Katello::ContentView.table_name}.default" => true).first
473
+ else
474
+ # this repo is part of a content view that was published from a user created view
475
+ self.content_view.get_repo_clone(env, self).first
476
+ end
477
+ end
478
+
479
+ # Returns true if the pulp_task_id was triggered by the last synchronization
480
+ # action for the repository. Dynflow action handles the synchronization
481
+ # by it's own so no need to synchronize it again in this callback. Since the
482
+ # callbacks are run just after synchronization is finished, it should be enough
483
+ # to check for the last synchronization task.
484
+ def dynflow_handled_last_sync?(pulp_task_id)
485
+ task = ForemanTasks::Task::DynflowTask.for_action(::Actions::Katello::Repository::Sync).
486
+ for_resource(self).order(:started_at).last
487
+ return task && task.main_action.pulp_task_id == pulp_task_id
488
+ end
489
+
490
+ def generate_content_path
491
+ path = content.content_url
492
+ root.substitutions.each do |key, value|
493
+ path = path.gsub("$#{key}", value) if value
494
+ end
495
+ path
496
+ end
497
+
498
+ def library_instance_or_self
499
+ self.library_instance || self
500
+ end
501
+
502
+ def generate_repo_path(content_path = nil)
503
+ _org, _content, content_path = library_instance_or_self.relative_path.split("/", 3) if content_path.blank?
504
+ content_path = content_path.sub(%r|^/|, '')
505
+ if self.environment
506
+ cve = ContentViewEnvironment.where(:environment_id => self.environment,
507
+ :content_view_id => self.content_view).first
508
+ "#{organization.label}/#{cve.label}/#{content_path}"
509
+ else
510
+ "#{organization.label}/#{ContentView::CONTENT_DIR}/#{self.content_view.label}/#{self.content_view_version.version}/#{content_path}"
511
+ end
512
+ end
513
+
514
+ def generate_docker_repo_path
515
+ org = self.organization.label.downcase
516
+ if self.environment
517
+ cve = ContentViewEnvironment.where(:environment_id => self.environment,
518
+ :content_view_id => self.content_view).first
519
+ view = self.content_view.label
520
+ product = self.product.label
521
+ env = cve.label.split('/').first
522
+ "#{org}-#{env.downcase}-#{view}-#{product}-#{self.root.label}"
523
+ else
524
+ "#{org}-#{self.content_view.label}-#{self.content_view_version.version}-#{self.root.product.label}-#{self.root.label}"
525
+ end
526
+ end
527
+
528
+ def packages_without_errata
529
+ if errata_filenames.any?
530
+ self.rpms.where("#{Rpm.table_name}.filename NOT in (?)", errata_filenames)
531
+ else
532
+ self.rpms
533
+ end
534
+ end
535
+
536
+ def module_streams_without_errata
537
+ module_stream_errata = Katello::ModuleStreamErratumPackage.joins(:erratum_package => {:erratum => :repository_errata})
538
+ .where("#{RepositoryErratum.table_name}.repository_id" => self.id)
539
+ .pluck("#{Katello::ModuleStreamErratumPackage.table_name}.module_stream_id")
540
+ if module_stream_errata.any?
541
+ self.module_streams.where("#{ModuleStream.table_name}.id NOT in (?)", module_stream_errata)
542
+ else
543
+ self.module_streams
544
+ end
545
+ end
546
+
547
+ def self.with_errata(errata)
548
+ joins(:repository_errata).where("#{Katello::RepositoryErratum.table_name}.erratum_id" => errata)
549
+ end
550
+
551
+ def errata_filenames
552
+ Katello::ErratumPackage.joins(:erratum => :repository_errata).
553
+ where("#{RepositoryErratum.table_name}.repository_id" => self.id).pluck("#{Katello::ErratumPackage.table_name}.filename")
554
+ end
555
+
556
+ # TODO: break up method
557
+ def build_clone(options)
558
+ to_env = options[:environment]
559
+ version = options[:version]
560
+ content_view = options[:content_view] || to_env.default_content_view
561
+ to_version = version || content_view.version(to_env)
562
+
563
+ fail _("Cannot clone into the Default Content View") if content_view.default?
564
+
565
+ if to_env && version
566
+ fail "Cannot clone into both an environment and a content view version archive"
567
+ end
568
+
569
+ if to_version.nil?
570
+ fail _("View %{view} has not been promoted to %{env}") %
571
+ {:view => content_view.name, :env => to_env.name}
572
+ end
573
+
574
+ if to_env && self.clones.in_content_views([content_view]).in_environment(to_env).any?
575
+ fail _("Repository has already been cloned to %{cv_name} in environment %{to_env}") %
576
+ {:to_env => to_env, :cv_name => content_view.name}
577
+ end
578
+
579
+ if self.yum?
580
+ if self.library_instance?
581
+ checksum_type = root.checksum_type || pulp_scratchpad_checksum_type
582
+ else
583
+ checksum_type = self.saved_checksum_type
584
+ end
585
+ end
586
+ clone = Repository.new(:environment => to_env,
587
+ :library_instance => library_instance_or_self,
588
+ :root => self.root,
589
+ :content_view_version => to_version,
590
+ :saved_checksum_type => checksum_type)
591
+
592
+ clone.relative_path = clone.docker? ? clone.generate_docker_repo_path : clone.generate_repo_path
593
+ clone
594
+ end
595
+
596
+ def self.synced_on_capsule(smart_proxy)
597
+ smart_proxy.smart_proxy_sync_histories.map { |sph| sph.repository unless sph.finished_at.nil? }
598
+ end
599
+
600
+ def clear_smart_proxy_sync_histories(smart_proxy = nil)
601
+ if smart_proxy
602
+ self.smart_proxy_sync_histories.where(:smart_proxy_id => smart_proxy.id).try(:delete_all)
603
+ else
604
+ self.smart_proxy_sync_histories.delete_all
605
+ end
606
+ end
607
+
608
+ def create_smart_proxy_sync_history(smart_proxy)
609
+ clear_smart_proxy_sync_histories(smart_proxy)
610
+ sp_history_args = {
611
+ :smart_proxy_id => smart_proxy.id,
612
+ :repository_id => self.id,
613
+ :started_at => Time.now
614
+ }
615
+ sp_history = ::Katello::SmartProxySyncHistory.create sp_history_args
616
+ sp_history.save!
617
+ sp_history.id
618
+ end
619
+
620
+ def latest_sync_audit
621
+ self.audits.where(:action => AUDIT_SYNC_ACTION).order(:created_at).last
622
+ end
623
+
624
+ def cancel_dynflow_sync
625
+ if latest_dynflow_sync
626
+ plan = latest_dynflow_sync.execution_plan
627
+
628
+ plan.steps.each_pair do |_number, step|
629
+ if step.cancellable? && step.is_a?(Dynflow::ExecutionPlan::Steps::RunStep)
630
+ ::ForemanTasks.dynflow.world.event(plan.id, step.id, Dynflow::Action::Cancellable::Cancel)
631
+ end
632
+ end
633
+ end
634
+ end
635
+
636
+ def latest_dynflow_sync
637
+ @latest_dynflow_sync ||= ForemanTasks::Task::DynflowTask.where(:label => ::Actions::Katello::Repository::Sync.name).
638
+ for_resource(self).order(:started_at).last
639
+ end
640
+
641
+ # returns other instances of this repo with the same library
642
+ # equivalent of repo
643
+ def environmental_instances(view)
644
+ self.all_instances.non_archived.in_content_views([view])
645
+ end
646
+
647
+ def archived_instance
648
+ if self.environment_id.nil? || self.library_instance_id.nil?
649
+ self
650
+ else
651
+ self.content_view_version.archived_repos.where(:root_id => self.root_id).first
652
+ end
653
+ end
654
+
655
+ def requires_yum_clone_distributor?
656
+ self.yum? && self.environment_id && !self.in_default_view?
657
+ end
658
+
659
+ def url?
660
+ root.url.present?
661
+ end
662
+
663
+ def related_resources
664
+ self.product
665
+ end
666
+
667
+ def node_syncable?
668
+ environment
669
+ end
670
+
671
+ def self.smart_proxy_syncable
672
+ joins(:content_view_version => :content_view).
673
+ merge(ContentView.ignore_generated).
674
+ where.not(environment_id: nil)
675
+ end
676
+
677
+ def exist_for_environment?(environment, content_view, attribute = nil)
678
+ if environment.present? && content_view.in_environment?(environment)
679
+ repos = content_view.version(environment).repos(environment)
680
+
681
+ repos.any? do |repo|
682
+ not_self = (repo.id != self.id)
683
+ same_product = (repo.product.id == self.product.id)
684
+
685
+ repo_exists = same_product && not_self
686
+
687
+ if repo_exists && attribute
688
+ same_attribute = repo.send(attribute) == self.send(attribute)
689
+ repo_exists = same_attribute
690
+ end
691
+
692
+ repo_exists
693
+ end
694
+ else
695
+ false
696
+ end
697
+ end
698
+
699
+ def units_for_removal(ids, type_class = nil)
700
+ removable_unit_association = unit_type_for_removal(type_class)
701
+ table_name = removable_unit_association.table_name
702
+ is_integer = Integer(ids.first) rescue false #assume all ids are either integers or not
703
+
704
+ if is_integer
705
+ removable_unit_association.where("#{table_name}.id in (?)", ids)
706
+ else
707
+ removable_unit_association.where("#{table_name}.pulp_id in (?)", ids)
708
+ end
709
+ end
710
+
711
+ def self.import_distributions
712
+ self.all.each do |repo|
713
+ repo.import_distribution_data
714
+ end
715
+ end
716
+
717
+ def import_distribution_data(target_repo = nil)
718
+ if target_repo
719
+ self.update!(
720
+ :distribution_version => target_repo.distribution_version,
721
+ :distribution_arch => target_repo.distribution_arch,
722
+ :distribution_family => target_repo.distribution_family,
723
+ :distribution_variant => target_repo.distribution_variant,
724
+ :distribution_bootable => target_repo.distribution_bootable
725
+ )
726
+ else
727
+ self.backend_service(SmartProxy.pulp_primary).import_distribution_data
728
+ end
729
+ end
730
+
731
+ def distribution_information
732
+ {
733
+ distribution_version: self.distribution_version,
734
+ distribution_arch: self.distribution_arch,
735
+ distribution_family: self.distribution_family,
736
+ distribution_variant: self.distribution_variant,
737
+ distribution_bootable: self.distribution_bootable
738
+ }
739
+ end
740
+
741
+ # deleteable? is already taken by the authorization mixin
742
+ def destroyable?(remove_from_content_view_versions = false)
743
+ if self.environment.try(:library?) && self.content_view.default?
744
+ if self.environment.organization.being_deleted?
745
+ return true
746
+ elsif self.custom? && self.deletable?(remove_from_content_view_versions)
747
+ return true
748
+ elsif !self.custom? && self.redhat_deletable?(remove_from_content_view_versions)
749
+ return true
750
+ else
751
+ errors.add(:base, _("Repository cannot be deleted since it has already been included in a published Content View. " \
752
+ "Please delete all Content View versions containing this repository before attempting to delete it."))
753
+
754
+ return false
755
+ end
756
+ end
757
+ return true
758
+ end
759
+
760
+ def sync_hook
761
+ run_callbacks :sync do
762
+ logger.debug "custom hook after_sync on #{name} will be executed if defined."
763
+ true
764
+ end
765
+ end
766
+
767
+ def rabl_path
768
+ "katello/api/v2/#{self.class.to_s.demodulize.tableize}/show"
769
+ end
770
+
771
+ def assert_deletable
772
+ throw :abort unless destroyable?
773
+ end
774
+
775
+ def docker_meta_tag_count
776
+ DockerMetaTag.in_repositories(self.id).count
777
+ end
778
+
779
+ # a primary repository actually has content (rpms, errata, etc) in the pulp repository. For these repositories, we can use the YumDistributor
780
+ # to generate metadata and can index the content from pulp, or for the case of content view archives without filters, can also use the YumCloneDistributor
781
+ #
782
+ def primary?
783
+ !self.yum? || # non-yum repos
784
+ self.in_default_view? || # default content view repos
785
+ (self.archive? && !self.content_view.composite) || # non-composite content view archive repos
786
+ (self.archive? && self.content_view.composite? && self.component_source_repositories.count > 1) # composite archive repo with more than 1 source repository
787
+ end
788
+
789
+ # a link repository has no content in the pulp repository and serves as a shell. It will always be empty. Only the YumCloneDistributor can be used
790
+ # to publish yum metadata, and it cannot be indexed from pulp, but must have its indexed associations copied from another repository (its target).
791
+ def link?
792
+ !primary?
793
+ end
794
+
795
+ # A link (empty repo) points to a target (a repository that actually has units in pulp). Target repos are always archive repos of a content view version (a repo with no environment)
796
+ # But for composite view versions, an archive repo, usually won't be a primary (but might be if multple components contain the same repo)
797
+ def target_repository
798
+ fail _("This is not a linked repository") if primary?
799
+ return nil if self.archived_instance.nil?
800
+
801
+ #this is an environment repo, and the archived_instance is a primary (not always true with composite)
802
+ if self.environment_id? && self.archived_instance.primary?
803
+ self.archived_instance
804
+ elsif self.environment_id #this is an environment repo, but a composite who's archived_instance isn't a primary
805
+ self.archived_instance.target_repository || self.archived_instance #to archived_instance if nil
806
+ else #must be a composite archive repo, with only one component repo
807
+ self.component_source_repositories.first
808
+ end
809
+ end
810
+
811
+ def component_source_repositories
812
+ #find other copies of this repositories, in the CV version's components, that are in the 'archive'
813
+ Katello::Repository.where(:content_view_version_id => self.content_view_version.components, :environment_id => nil,
814
+ :root_id => self.root_id)
815
+ end
816
+
817
+ def self.linked_repositories
818
+ to_return = []
819
+ Katello::Repository.yum_type.in_non_default_view.find_each do |repo|
820
+ to_return << repo if repo.link?
821
+ end
822
+ to_return
823
+ end
824
+
825
+ def self.search_by_redhat(_key, operator, value)
826
+ value = value == 'true'
827
+ value = !value if operator == '<>'
828
+
829
+ product_ids = Katello::Product.redhat.select(:id)
830
+ root_ids = Katello::RootRepository.where(:product_id => product_ids).pluck(:id)
831
+ if product_ids.empty?
832
+ {:conditions => "1=0"}
833
+ else
834
+ operator = value ? 'IN' : 'NOT IN'
835
+ {:conditions => "#{Katello::Repository.table_name}.root_id #{operator} (#{root_ids.join(',')})"}
836
+ end
837
+ end
838
+
839
+ def self.search_by_content_label(_key, operator, value)
840
+ conditions = sanitize_sql_for_conditions(["label #{operator} ?", value_to_sql(operator, value)])
841
+ contents = Katello::Content.where(conditions).pluck(:cp_content_id)
842
+ root_ids = Katello::RootRepository.where(:content_id => contents).pluck(:id)
843
+ if root_ids.empty?
844
+ { :conditions => "1=0" }
845
+ else
846
+ { :conditions => "#{Katello::Repository.table_name}.root_id IN (#{root_ids.join(',')})" }
847
+ end
848
+ end
849
+
850
+ def self.safe_render_container_name(repository, pattern = nil)
851
+ if (pattern && !pattern.blank?) || (repository.environment && !repository.environment.registry_name_pattern.empty?)
852
+ pattern ||= repository.environment.registry_name_pattern
853
+ allowed_methods = {}
854
+ allowed_vars = {}
855
+ scope_variables = {repository: repository, organization: repository.organization, product: repository.product,
856
+ lifecycle_environment: repository.environment, content_view: repository.content_view_version.content_view,
857
+ content_view_version: repository.content_view_version}
858
+ box = Safemode::Box.new(repository, allowed_methods)
859
+ erb = ERB.new(pattern)
860
+ pattern = box.eval(erb.src, allowed_vars, scope_variables)
861
+ return Repository.clean_container_name(pattern)
862
+ elsif repository.content_view.default?
863
+ items = [repository.organization.label, repository.product.label, repository.label]
864
+ elsif repository.environment
865
+ items = [repository.organization.label, repository.environment.label, repository.content_view.label, repository.product.label, repository.label]
866
+ else
867
+ items = [repository.organization.label, repository.content_view.label, repository.content_view_version.version, repository.product.label, repository.label]
868
+ end
869
+ Repository.clean_container_name(items.compact.join("-"))
870
+ end
871
+
872
+ def self.clean_container_name(name)
873
+ name.gsub(/[^-\/\w]/, "_").gsub(/_{3,}/, "_").gsub(/-_|^_+|_+$/, "").downcase.strip
874
+ end
875
+
876
+ def custom_repo_path
877
+ return custom_docker_repo_path if docker?
878
+ if [environment, product, root.label].any?(&:nil?)
879
+ return nil # can't generate valid path
880
+ end
881
+ prefix = [environment.organization.label, environment.label].map { |x| x.gsub(/[^-\w]/, "_") }.join("/")
882
+ prefix + root.custom_content_path
883
+ end
884
+
885
+ def custom_docker_repo_path
886
+ if [environment, product, root.label].any?(&:nil?)
887
+ return nil # can't generate valid path
888
+ end
889
+ parts = [environment.organization.label, product.label, root.label]
890
+ parts.map { |x| x.gsub(/[^-\w]/, "_") }.join("-").downcase
891
+ end
892
+
893
+ def copy_indexed_data(source_repository)
894
+ repository_type.content_types_to_index.each do |type|
895
+ type.model_class.copy_repository_associations(source_repository, self)
896
+ repository_type.index_additional_data_proc&.call(self, source_repository)
897
+ end
898
+ end
899
+
900
+ def index_linked_repo
901
+ if (base_repo = self.target_repository)
902
+ copy_indexed_data(base_repo)
903
+ else
904
+ Rails.logger.error("Cannot index #{self.id}, no target repository found.")
905
+ end
906
+ end
907
+
908
+ def index_content(options = {})
909
+ # set full_index to true if you want to force fetch all data from pulp
910
+ # This is automatically done for library instance repos
911
+ # However for non-library instance as in those belonging to a version
912
+ # by default we fetch only ids and match them with the library instance
913
+ # some times we want to force fetch all data even for non-library repos.
914
+ # Use the full_index for those times
915
+
916
+ full_index = options.fetch(:full_index, false)
917
+ source_repository = options.fetch(:source_repository, nil)
918
+ if self.yum? && !self.primary?
919
+ index_linked_repo
920
+ elsif source_repository && !repository_type.unique_content_per_repo
921
+ copy_indexed_data(source_repository)
922
+ else
923
+ repository_type.content_types_to_index.each do |type|
924
+ Katello::Logging.time("CONTENT_INDEX", data: {type: type.model_class}) do
925
+ Katello::ContentUnitIndexer.new(content_type: type, repository: self, optimized: !full_index).import_all
926
+ end
927
+ end
928
+ repository_type.index_additional_data_proc&.call(self)
929
+ end
930
+ true
931
+ end
932
+
933
+ def in_content_view?(content_view)
934
+ content_view.repositories.include? self
935
+ end
936
+
937
+ protected
938
+
939
+ def unit_type_for_removal(type_class = nil)
940
+ if type_class
941
+ Katello::RepositoryTypeManager.find_content_type(type_class).model_class
942
+ else
943
+ Katello::RepositoryTypeManager.find(self.content_type).default_managed_content_type.model_class
944
+ end
945
+ end
946
+
947
+ def downcase_pulp_id
948
+ # Docker doesn't support uppercase letters in repository names. Since the pulp_id
949
+ # is currently being used for the name, it will be downcased for this content type.
950
+ if self.content_type == Repository::DOCKER_TYPE
951
+ self.pulp_id = self.pulp_id.downcase
952
+ end
953
+ end
954
+
955
+ def remove_docker_content(manifests)
956
+ destroyable_manifests = manifests.select do |manifest|
957
+ manifest.repositories.empty? || manifest.docker_manifest_lists.empty?
958
+ end
959
+ # destroy any orphan docker manifests
960
+ destroyable_manifests.each do |manifest|
961
+ self.docker_manifests.delete(manifest)
962
+ manifest.destroy
963
+ end
964
+ DockerMetaTag.cleanup_tags
965
+ end
966
+
967
+ apipie :class, desc: "A class representing #{model_name.human} object" do
968
+ name 'Repository'
969
+ refs 'Repository'
970
+ sections only: %w[all additional]
971
+ prop_group :katello_basic_props, Katello::Model, meta: { friendly_name: 'Repository' }
972
+ property :docker_upstream_name, String, desc: 'Returns name of the upstream docker repository'
973
+ end
974
+ class Jail < ::Safemode::Jail
975
+ allow :name, :label, :docker_upstream_name, :content_counts
976
+ end
977
+ end
978
+ end