katello 4.0.1 → 4.0.2.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +3 -3
  3. data/app/controllers/katello/api/v2/repositories_controller.rb +1 -0
  4. data/app/lib/actions/katello/capsule_content/sync.rb +1 -1
  5. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +44 -6
  6. data/app/lib/actions/katello/content_view_version/incremental_update.rb +12 -10
  7. data/app/lib/actions/katello/orphan_cleanup/remove_orphans.rb +2 -2
  8. data/app/lib/actions/katello/repository/check_matching_content.rb +3 -4
  9. data/app/lib/actions/pulp3/content_guard/refresh.rb +6 -10
  10. data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -1
  11. data/app/lib/katello/errors.rb +1 -1
  12. data/app/lib/katello/util/hostgroup_facets_helper.rb +126 -0
  13. data/app/models/katello/authorization/content_view.rb +12 -0
  14. data/app/models/katello/authorization/repository.rb +18 -0
  15. data/app/models/katello/concerns/hostgroup_extensions.rb +3 -1
  16. data/app/models/katello/concerns/redhat_extensions.rb +18 -10
  17. data/app/models/katello/concerns/smart_proxy_extensions.rb +23 -0
  18. data/app/models/katello/glue/pulp/repo.rb +0 -19
  19. data/app/models/katello/ping.rb +2 -9
  20. data/app/models/katello/repository.rb +14 -0
  21. data/app/services/cert/certs.rb +4 -0
  22. data/app/services/katello/pulp3/api/content_guard.rb +39 -5
  23. data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +1 -0
  24. data/app/views/overrides/smart_proxies/_download_policy.erb +1 -1
  25. data/db/migrate/20200514092553_move_katello_fields_from_hostgroups.katello.rb +1 -1
  26. data/db/migrate/20210119162528_delete_puppet_and_ostree_repos.rb +25 -22
  27. data/db/migrate/20210512192745_fix_red_hat_root_repository_arch.rb +11 -0
  28. data/lib/katello.rb +1 -1
  29. data/lib/katello/engine.rb +0 -1
  30. data/lib/katello/tasks/fix_hostgroup_facets.rake +8 -0
  31. data/lib/katello/version.rb +1 -1
  32. data/lib/proxy_api/container_gateway.rb +22 -11
  33. metadata +25 -9
  34. data/app/lib/actions/pulp3/capsule_content/refresh_content_guard.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02ff428534ded98d248efb23e9684364f8930cb1175c25a70ad3529963e435a0
4
- data.tar.gz: 680e66df3f38df78a5feface53e92cc4129cc4a847fbd5e344ab63e5dd6bfd40
3
+ metadata.gz: 60cb9d2337d887d068fbe7f6886e157de5c3c35518a2ad3ef78e9a384ed8cb50
4
+ data.tar.gz: 34cb737891925c363205d7464793bedf3cc8f80a8ed5778c056962d0f3f9a46f
5
5
  SHA512:
6
- metadata.gz: 32ccbc77851e99ff2e16ebb36219871a0e086f86f60da1d3265e26cddbfdb7563c1363fc72ba5d3db34fb8360eb01ddb678fc882a41a16e635397631ab6b767e
7
- data.tar.gz: 882219885a8aa1dd105a6b24a630092984d462fcac3a2030fa8fb3e6c112225ac48b00e746f7194bbb4e5ff2fb1d8766610b65434e0555958b4b251668be4cf6
6
+ metadata.gz: fbb101788bbe74c6829b244b9e3f48ef9f04b7c059b3410d9ab563c1b44ee4d35aa1e76579f99a605bd122005f9b1112ccbf3e9a85c3d018e72a7a184ddd7476
7
+ data.tar.gz: e48ae02b0e60e82a9b8b27e56662940728de4f8c564492bd0e4681e39824d42934ce16a38c1ca2cdfe69e4da19ca3b19081723805464a1873cf898c6b40a8893
@@ -106,7 +106,7 @@ module Katello
106
106
  return nil unless params[:repository]
107
107
  repository = Repository.docker_type.find_by(container_repository_name: params[:repository])
108
108
  if require_user_authorization?(repository)
109
- repository = readable_repositories.docker_type.find_by(container_repository_name: params[:repository])
109
+ repository = Repository.readable_docker_catalog.find_by(container_repository_name: params[:repository])
110
110
  end
111
111
  repository
112
112
  end
@@ -331,7 +331,7 @@ module Katello
331
331
  params[:per_page] = params[:n] || 25
332
332
  params[:search] = params[:q]
333
333
 
334
- search_results = scoped_search(readable_repositories.docker_type.distinct,
334
+ search_results = scoped_search(Repository.readable_docker_catalog.distinct,
335
335
  :container_repository_name, :asc, options)
336
336
 
337
337
  results = {
@@ -345,7 +345,7 @@ module Katello
345
345
  end
346
346
 
347
347
  def catalog
348
- repositories = readable_repositories.docker_type.collect do |repository|
348
+ repositories = Repository.readable_docker_catalog.collect do |repository|
349
349
  repository.container_repository_name
350
350
  end
351
351
  render json: { repositories: repositories }
@@ -95,6 +95,7 @@ module Katello
95
95
  :required => false
96
96
  param :with_content, RepositoryTypeManager.enabled_content_types, :desc => N_("only repositories having at least one of the specified content type ex: rpm , erratum")
97
97
  param :download_policy, ::Runcible::Models::YumImporter::DOWNLOAD_POLICIES, :desc => N_("limit to only repositories with this download policy")
98
+ param :username, String, :desc => N_("only show the repositories readable by this user with this username")
98
99
  param_group :search, Api::V2::ApiController
99
100
  add_scoped_search_description_for(Repository)
100
101
  def index
@@ -39,7 +39,7 @@ module Actions
39
39
  end
40
40
 
41
41
  if smart_proxy.has_feature?(SmartProxy::PULP3_FEATURE)
42
- plan_action(Actions::Pulp3::CapsuleContent::RefreshContentGuard, smart_proxy)
42
+ plan_action(Actions::Pulp3::ContentGuard::Refresh, smart_proxy)
43
43
  plan_action(Actions::Pulp3::Orchestration::Repository::RefreshRepos, smart_proxy, refresh_options)
44
44
  end
45
45
  plan_action(SyncCapsule, smart_proxy, refresh_options)
@@ -35,15 +35,53 @@ module Actions
35
35
  end
36
36
  end
37
37
  end
38
- update_unauthenticated_repo_list(smart_proxy) if smart_proxy.has_feature?("Container_Gateway")
38
+ sync_container_gateway(smart_proxy)
39
39
  end
40
40
 
41
- def update_unauthenticated_repo_list(smart_proxy)
42
- unauthenticated_repo_list =
43
- ::Katello::SmartProxyHelper.new(smart_proxy).combined_repos_available_to_capsule.select do |repo|
44
- repo.docker? && repo.environment.registry_unauthenticated_pull
41
+ def sync_container_gateway(smart_proxy)
42
+ if smart_proxy.has_feature?(::SmartProxy::CONTAINER_GATEWAY_FEATURE)
43
+ update_container_repo_list(smart_proxy)
44
+ users = smart_proxy.container_gateway_users
45
+ update_user_container_repo_mapping(smart_proxy, users) if users.any?
46
+ end
47
+ end
48
+
49
+ def unauthenticated_container_repositories
50
+ ::Katello::Repository.joins(:environment).where("#{::Katello::KTEnvironment.table_name}.registry_unauthenticated_pull" => true).select(:id).pluck(:id)
51
+ end
52
+
53
+ def update_container_repo_list(smart_proxy)
54
+ # [{ repository: "repoA", auth_required: false }]
55
+ repo_list = []
56
+ ::Katello::SmartProxyHelper.new(smart_proxy).combined_repos_available_to_capsule.each do |repo|
57
+ if repo.docker? && !repo.container_repository_name.nil?
58
+ repo_list << { repository: repo.container_repository_name,
59
+ auth_required: !unauthenticated_container_repositories.include?(repo.id) }
45
60
  end
46
- smart_proxy.update_unauthenticated_repo_list(unauthenticated_repo_list.map(&:container_repository_name))
61
+ end
62
+ smart_proxy.update_container_repo_list(repo_list)
63
+ end
64
+
65
+ def update_user_container_repo_mapping(smart_proxy, users)
66
+ # Example user-repo mapping:
67
+ # { users:
68
+ # [
69
+ # 'user a' => [{ repository: 'repo 1', auth_required: true }]
70
+ # ]
71
+ # }
72
+
73
+ user_repo_map = { users: [] }
74
+ users.each do |user|
75
+ inner_repo_list = []
76
+ repositories = ::Katello::Repository.readable_docker_catalog_as(user)
77
+ repositories.each do |repo|
78
+ next if repo.container_repository_name.nil?
79
+ inner_repo_list << { repository: repo.container_repository_name,
80
+ auth_required: !unauthenticated_container_repositories.include?(repo.id) }
81
+ end
82
+ user_repo_map[:users] << { user.login => inner_repo_list }
83
+ end
84
+ smart_proxy.update_user_container_repo_mapping(user_repo_map)
47
85
  end
48
86
 
49
87
  def repos_to_sync(smart_proxy, environment, content_view, repository, skip_metatadata_check = false)
@@ -19,7 +19,8 @@ module Actions
19
19
 
20
20
  # rubocop:disable Metrics/MethodLength
21
21
  # rubocop:disable Metrics/AbcSize
22
- def plan(old_version, environments, options = {}) # rubocop:disable Metrics/CyclomaticComplexity
22
+ # rubocop:disable Metrics/CyclomaticComplexity
23
+ def plan(old_version, environments, options = {})
23
24
  dep_solve = options.fetch(:resolve_dependencies, true)
24
25
  description = options.fetch(:description, '')
25
26
  content = options.fetch(:content, {})
@@ -72,8 +73,15 @@ module Actions
72
73
  unit_map = pulp3_content_mapping(content)
73
74
 
74
75
  unless extended_repo_mapping.empty? || unit_map.values.flatten.empty?
75
- copy_action_outputs << plan_action(Pulp3::Repository::MultiCopyUnits, extended_repo_mapping, unit_map,
76
- dependency_solving: dep_solve).output
76
+ sequence do
77
+ copy_action_outputs << plan_action(Pulp3::Repository::MultiCopyUnits, extended_repo_mapping, unit_map,
78
+ dependency_solving: dep_solve).output
79
+ repos_to_clone.each do |source_repos|
80
+ if separated_repo_map[:pulp3_yum].keys.include?(source_repos)
81
+ copy_repos(repository_mapping[source_repos])
82
+ end
83
+ end
84
+ end
77
85
  end
78
86
  end
79
87
 
@@ -150,15 +158,9 @@ module Actions
150
158
  end
151
159
  end
152
160
 
153
- def copy_repos(new_repo, new_version, content, dep_solve)
161
+ def copy_repos(new_repo, _new_version = nil, _content = nil, _dep_solve = nil)
154
162
  copy_output = []
155
163
  sequence do
156
- solve_dependencies = new_version.content_view.solve_dependencies || dep_solve
157
- copy_output += copy_deb_content(new_repo, solve_dependencies, content[:deb_ids])
158
- copy_output += copy_yum_content(new_repo, solve_dependencies,
159
- content[:package_ids],
160
- content[:errata_ids])
161
-
162
164
  plan_action(Katello::Repository::MetadataGenerate, new_repo)
163
165
  plan_action(Katello::Repository::IndexContent, id: new_repo.id)
164
166
  end
@@ -1,13 +1,13 @@
1
1
  module Actions
2
2
  module Katello
3
3
  module OrphanCleanup
4
- class RemoveOrphans < Pulp::Abstract
4
+ class RemoveOrphans < Actions::Base
5
5
  input_format do
6
6
  param :capsule_id
7
7
  end
8
8
  def plan(proxy)
9
9
  sequence do
10
- plan_action(Actions::Pulp::Orchestration::OrphanCleanup::RemoveOrphans, proxy)
10
+ plan_action(Actions::Pulp::Orchestration::OrphanCleanup::RemoveOrphans, proxy) if (proxy.has_feature?(SmartProxy::PULP_FEATURE) || proxy.has_feature?(SmartProxy::PULP_NODE_FEATURE))
11
11
  if proxy.pulp3_enabled?
12
12
  plan_action(
13
13
  Actions::Pulp3::Orchestration::OrphanCleanup::RemoveOrphans,
@@ -13,6 +13,7 @@ module Actions
13
13
  def run
14
14
  source_repo = ::Katello::Repository.find(input[:source_repo_id])
15
15
  target_repo = ::Katello::Repository.find(input[:target_repo_id])
16
+ target_repo_published = target_repo.backend_service(SmartProxy.pulp_primary).published?
16
17
 
17
18
  if source_repo.content_type == ::Katello::Repository::YUM_TYPE
18
19
  srpms_match = srpms_match?(source_repo, target_repo)
@@ -23,16 +24,14 @@ module Actions
23
24
  yum_metadata_files = yum_metadata_files_match?(source_repo, target_repo)
24
25
  checksum_match = (target_repo.saved_checksum_type == source_repo.saved_checksum_type)
25
26
 
26
- published = target_repo.backend_service(SmartProxy.pulp_primary).published?
27
-
28
27
  output[:checksum_match] = checksum_match
29
- output[:matching_content] = yum_metadata_files && srpms_match && rpms && errata && package_groups && distributions && published && checksum_match
28
+ output[:matching_content] = yum_metadata_files && srpms_match && rpms && errata && package_groups && distributions && target_repo_published && checksum_match
30
29
  end
31
30
 
32
31
  if source_repo.content_type == ::Katello::Repository::DEB_TYPE
33
32
  debs = debs_match?(source_repo, target_repo)
34
33
 
35
- output[:matching_content] = debs && target_repo.published?
34
+ output[:matching_content] = debs && target_repo_published
36
35
  end
37
36
  end
38
37
 
@@ -2,16 +2,12 @@ module Actions
2
2
  module Pulp3
3
3
  module ContentGuard
4
4
  class Refresh < Pulp3::Abstract
5
- def plan(smart_proxy, options = {})
6
- return if (::Katello::Pulp3::ContentGuard.count > 0 || options.try(:[], :update))
7
- content_guard_api = ::Katello::Pulp3::Api::ContentGuard.new(smart_proxy)
8
- if options.try(:[], :update)
9
- content_guard_href = ::Katello::Pulp3::ContentGuard.first.href
10
- content_guard_api.partial_update content_guard_href
11
- else
12
- content_guard_api.create
13
- ::Katello::Pulp3::ContentGuard.import(smart_proxy)
14
- end
5
+ def plan(smart_proxy)
6
+ plan_self(smart_proxy_id: smart_proxy.id)
7
+ end
8
+
9
+ def run
10
+ ::Katello::Pulp3::Api::ContentGuard.new(smart_proxy).refresh
15
11
  end
16
12
  end
17
13
  end
@@ -2,7 +2,7 @@ module Actions
2
2
  module Pulp3
3
3
  module Orchestration
4
4
  module OrphanCleanup
5
- class RemoveOrphans < Pulp::Abstract
5
+ class RemoveOrphans < Pulp3::Abstract
6
6
  def plan(proxy)
7
7
  if proxy.pulp3_enabled?
8
8
  sequence do
@@ -53,7 +53,7 @@ module Katello
53
53
 
54
54
  class PulpcoreMissingCapabilities < StandardError
55
55
  def message
56
- _("A smart proxy seems to have been refreshed without pulpcore being running. You may want to ")
56
+ _("A smart proxy seems to have been refreshed without pulpcore being running. Please refresh the smart proxy after ensuring that pulpcore services are running.")
57
57
  end
58
58
  end
59
59
 
@@ -0,0 +1,126 @@
1
+ # Used exclusively by fix_hostgroup_facets.rake task
2
+ module Katello
3
+ module Util
4
+ class HostgroupFacetsHelper
5
+ def initialize
6
+ @logger = Logger.new($stdout)
7
+ end
8
+
9
+ def interested_hostgroups
10
+ groups = ::Hostgroup.unscoped.where(
11
+ id: Katello::Hostgroup::ContentFacet.
12
+ where(content_source_id: nil,
13
+ kickstart_repository_id: nil,
14
+ content_view_id: nil,
15
+ lifecycle_environment_id: nil).select(:hostgroup_id))
16
+ parents = groups.select { |group| group.parent.blank? }
17
+ children = groups.reject { |group| group.parent.blank? }
18
+ # we want the parents to get created before the children
19
+ # hence the order
20
+ parents + children
21
+ end
22
+
23
+ def pick_facet_values(hg)
24
+ # This call looks at the audit logs for a host group.
25
+ # Pries out information related to lce, ks, cv and content_source_id from the audit logs.
26
+ # The audit logs typically only contain updates.
27
+ # So if the user changed just the content_view_id, then that is the only thing marked as audited_changes.
28
+ # Hence we need to go through all the audit logs until we have information on lce, ks, cv and cs.
29
+ # If there was only one audit log and that was during the creation of hostgroup
30
+ # the audited changes look like this
31
+ # ```ruby
32
+ # {
33
+ # content_view_id: 10,
34
+ # kickstart_repository_id: 1000
35
+ # ......
36
+ # }
37
+ # ```
38
+ # However if you updated the hostgroup and set the kickstart_repository_id, or
39
+ # content_view_id then audited changes look like
40
+ # ```ruby
41
+ # {
42
+ # content_view_id: [10, 11],
43
+ # kickstart_repository_id: [1000, 1200]
44
+ # ......
45
+ # }
46
+ # ```
47
+ # So the code says "if the attribute value is an array pick the last value else just keep the value as it is "
48
+
49
+ # Further along it is to be noted that `hostgroup.audits` returns the audits ordered by the version number in ascending order, so the latest audit will be `hostgroup.audits.last`
50
+
51
+ # We want to iterate though each audit from latest audit to start, and as soon as we find a content_view_id key or kickstart_repository_id key or lifecycle environment_id key or content_source_id key we want it to be set once.
52
+
53
+ # So if I had an audit history like
54
+ # ``` ruby
55
+ # {
56
+ # content_view_id: 10,
57
+ # kickstart_repository_id: 1000,
58
+ # version:1
59
+ # ......
60
+ # },
61
+ # {
62
+ # content_view_id: [10, 11],
63
+ # kickstart_repository_id: [1000, 1200],
64
+ # version: 2
65
+ # ......
66
+ # }
67
+ # ```
68
+
69
+ # The code would start at version 2, notice that cv_id and ks_repo were set there
70
+ # and keep them as the final.
71
+ # So when it goes to version 1 since cv_id and ks_repo are already set,
72
+ # it will ignore. It will finally
73
+ # return {content_view_id: 11, kickstart_repository_id: 1200}
74
+ facet_values = {}
75
+ hg.audits.reverse_each do |audit|
76
+ hg_changes = audit.audited_changes.slice("lifecycle_environment_id",
77
+ "kickstart_repository_id",
78
+ "content_view_id",
79
+ "content_source_id")
80
+ facet_values = hg_changes.merge(facet_values)
81
+ end
82
+
83
+ values = facet_values.map do |k, v|
84
+ v = v[-1] if v.is_a? Array
85
+ [k, v]
86
+ end
87
+ values.to_h.with_indifferent_access
88
+ end
89
+
90
+ def main
91
+ bad_hgs = []
92
+ good_hgs = []
93
+
94
+ groups = interested_hostgroups.each do |hg|
95
+ facet = hg.content_facet
96
+ values = pick_facet_values(hg)
97
+ if !values.empty? && facet.update(values)
98
+ good_hgs << { hostgroup: hg, facet_values: values }
99
+ else
100
+ bad_hgs << { hostgroup: hg, facet_values: values }
101
+ facet.save(validate: false)
102
+ end
103
+ end
104
+
105
+ unless bad_hgs.empty?
106
+ @logger.warn "Some of the hostgroups reported a validation error. "\
107
+ "The hostgroups have been updated. "\
108
+ "Check via the Web UI."
109
+
110
+ bad_hgs.each do |bad_group|
111
+ @logger.warn "Hostgroup #{bad_group[:hostgroup]}"
112
+ @logger.warn "Facet Values #{bad_group[:facet_values]}"
113
+ end
114
+ end
115
+ unless good_hgs.empty?
116
+ @logger.info "Following hostgroups were succesfully updated."
117
+ good_hgs.each do |good_group|
118
+ @logger.info "Hostgroup #{good_group[:hostgroup]}"
119
+ @logger.info "Facet Values #{good_group[:facet_values]}"
120
+ end
121
+ end
122
+ @logger.info("#{groups.count} Hostgroup(s) were updated.")
123
+ end
124
+ end
125
+ end
126
+ end
@@ -33,6 +33,18 @@ module Katello
33
33
  authorized(:view_content_views)
34
34
  end
35
35
 
36
+ def creatable
37
+ authorized(:view_content_views)
38
+ end
39
+
40
+ def importable?
41
+ creatable? && publishable?
42
+ end
43
+
44
+ def readable_as(user)
45
+ authorized_as(user, :view_content_views)
46
+ end
47
+
36
48
  def readable?
37
49
  ::User.current.can?(:view_content_views)
38
50
  end
@@ -27,6 +27,24 @@ module Katello
27
27
  joins(:root).where("#{Repository.table_name}.id in (?) or #{self.table_name}.id in (?) or #{self.table_name}.id in (?) or #{self.table_name}.id in (?)", in_products, in_content_views, in_versions, in_environments)
28
28
  end
29
29
 
30
+ def readable_as(user)
31
+ in_products = Repository.in_product(Katello::Product.authorized_as(user, :view_products)).select(:id)
32
+ in_environments = Repository.where(:environment_id => Katello::KTEnvironment.authorized_as(user, :view_lifecycle_environments)).select(:id)
33
+ in_content_views = Repository.joins(:content_view_repositories).where("#{ContentViewRepository.table_name}.content_view_id" => Katello::ContentView.readable_as(user)).select(:id)
34
+ in_versions = Repository.joins(:content_view_version).where("#{Katello::ContentViewVersion.table_name}.content_view_id" => Katello::ContentView.readable_as(user)).select(:id)
35
+ joins(:root).where("#{Repository.table_name}.id in (?) or #{self.table_name}.id in (?) or #{self.table_name}.id in (?) or #{self.table_name}.id in (?)", in_products, in_content_views, in_versions, in_environments)
36
+ end
37
+
38
+ def readable_docker_catalog
39
+ readable_docker_catalog_as(User.current)
40
+ end
41
+
42
+ def readable_docker_catalog_as(user)
43
+ table_name = Repository.table_name
44
+ in_unauth_environments = Repository.joins(:environment).where("#{Katello::KTEnvironment.table_name}.registry_unauthenticated_pull" => true).select(:id)
45
+ Repository.readable_as(user).or(Repository.joins(:root).where("#{table_name}.id in (?)", in_unauth_environments)).non_archived.docker_type
46
+ end
47
+
30
48
  def exportable
31
49
  in_product(Katello::Product.exportable)
32
50
  end
@@ -107,7 +107,9 @@ module Katello
107
107
  return true unless operatingsystem
108
108
 
109
109
  if operatingsystem.respond_to? :kickstart_repos
110
- return operatingsystem.kickstart_repos(self).any? { |repo| repo[:id] == (content_facet&.kickstart_repository_id || content_facet&.kickstart_repository&.id) }
110
+ operatingsystem.kickstart_repos(self, content_facet: content_facet).any? do |repo|
111
+ repo[:id] == (content_facet&.kickstart_repository_id || content_facet&.kickstart_repository&.id)
112
+ end
111
113
  end
112
114
  end
113
115
 
@@ -10,8 +10,15 @@ module Katello
10
10
  minor ||= '' # treat minor versions as empty string to not confuse with nil
11
11
  os = ::Redhat.where(:name => os_name, :major => major, :minor => minor).try(:first)
12
12
  return os if os
13
- description = "#{os_name}-#{repo.distribution_version}"
14
- create_os = lambda { ::Redhat.create!(:name => os_name, :major => major, :minor => minor, :description => description) }
13
+
14
+ if ::Redhat.where(:title => "#{os_name} #{repo.distribution_version}").present?
15
+ description = "#{os_name} #{repo.distribution_version} #{SecureRandom.uuid}"
16
+ create_os = lambda do
17
+ ::Redhat.create!(:name => os_name, :major => major, :minor => minor, :description => description)
18
+ end
19
+ else
20
+ create_os = lambda { ::Redhat.create!(:name => os_name, :major => major, :minor => minor) }
21
+ end
15
22
 
16
23
  begin
17
24
  create_os.call
@@ -42,10 +49,11 @@ module Katello
42
49
  end
43
50
  end
44
51
 
45
- def kickstart_repos(host)
46
- distros = distribution_repositories(host).where(distribution_bootable: true)
47
- if distros && host&.content_facet&.content_source
48
- distros.map { |distro| distro.to_hash(host.content_facet.content_source) }
52
+ def kickstart_repos(host, content_facet: nil)
53
+ distros = distribution_repositories(host, content_facet: content_facet).where(distribution_bootable: true)
54
+ content_facet ||= host.content_facet
55
+ if distros && content_facet&.content_source
56
+ distros.map { |distro| distro.to_hash(content_facet.content_source) }
49
57
  else
50
58
  []
51
59
  end
@@ -62,10 +70,10 @@ module Katello
62
70
  end
63
71
  end
64
72
 
65
- def distribution_repositories(host)
66
- content_view = host.try(:content_facet).try(:content_view) || host.try(:content_view)
67
- lifecycle_environment = host.try(:content_facet).try(:lifecycle_environment) || host.try(:lifecycle_environment)
68
-
73
+ def distribution_repositories(host, content_facet: nil)
74
+ content_facet ||= host.content_facet
75
+ content_view = content_facet.try(:content_view) || host.try(:content_view)
76
+ lifecycle_environment = content_facet.try(:lifecycle_environment) || host.try(:lifecycle_environment)
69
77
  if content_view && lifecycle_environment && host.os && host.architecture
70
78
  Katello::Repository.in_environment(lifecycle_environment).in_content_views([content_view]).
71
79
  where(:distribution_arch => host.architecture.name).
@@ -18,6 +18,7 @@ module Katello
18
18
  PULP3_FEATURE = "Pulpcore".freeze
19
19
  PULP_FEATURE = "Pulp".freeze
20
20
  PULP_NODE_FEATURE = "Pulp Node".freeze
21
+ CONTAINER_GATEWAY_FEATURE = "Container_Gateway".freeze
21
22
 
22
23
  DOWNLOAD_INHERIT = 'inherit'.freeze
23
24
  DOWNLOAD_POLICIES = ::Runcible::Models::YumImporter::DOWNLOAD_POLICIES + [DOWNLOAD_INHERIT]
@@ -89,6 +90,10 @@ module Katello
89
90
  end
90
91
 
91
92
  def self.with_environment(environment, include_default = false)
93
+ (pulp2_proxies_with_environment(environment, include_default) + pulpcore_proxies_with_environment(environment)).try(:uniq)
94
+ end
95
+
96
+ def self.pulp2_proxies_with_environment(environment, include_default = false)
92
97
  features = [PULP_NODE_FEATURE]
93
98
  features << PULP_FEATURE if include_default
94
99
 
@@ -96,6 +101,11 @@ module Katello
96
101
  where(katello_capsule_lifecycle_environments: { lifecycle_environment_id: environment.id })
97
102
  end
98
103
 
104
+ def self.pulpcore_proxies_with_environment(environment)
105
+ unscoped.where(id: unscoped.select { |p| p.pulp_mirror? }.pluck(:id)).joins(:capsule_lifecycle_environments).
106
+ where(katello_capsule_lifecycle_environments: { lifecycle_environment_id: environment.id })
107
+ end
108
+
99
109
  def self.sync_needed?(environment)
100
110
  Setting[:foreman_proxy_content_auto_sync] && unscoped.with_environment(environment).any?
101
111
  end
@@ -119,6 +129,19 @@ module Katello
119
129
  path
120
130
  end
121
131
 
132
+ def update_container_repo_list(repo_list)
133
+ ProxyAPI::ContainerGateway.new(url: self.url).repository_list({ repositories: repo_list })
134
+ end
135
+
136
+ def update_user_container_repo_mapping(user_repo_map)
137
+ ProxyAPI::ContainerGateway.new(url: self.url).user_repository_mapping(user_repo_map)
138
+ end
139
+
140
+ def container_gateway_users
141
+ usernames = ProxyAPI::ContainerGateway.new(url: self.url).users
142
+ ::User.where(login: usernames['users'])
143
+ end
144
+
122
145
  def pulp_url
123
146
  uri = URI.parse(url)
124
147
  "#{uri.scheme}://#{uri.host}/pulp/api/v2/"
@@ -74,11 +74,6 @@ module Katello
74
74
  pulp_repo_facts['content_unit_counts']['srpm']
75
75
  end
76
76
 
77
- def uri
78
- uri = URI.parse(SETTINGS[:katello][:pulp][:url])
79
- "https://#{uri.host}/pulp/content/#{relative_path}"
80
- end
81
-
82
77
  def to_hash
83
78
  pulp_repo_facts.merge(as_json).merge(:sync_state => sync_state)
84
79
  end
@@ -364,19 +359,5 @@ module Katello
364
359
  end
365
360
  end
366
361
  end
367
-
368
- def full_path(smart_proxy = nil, force_http = false)
369
- pulp_uri = URI.parse(smart_proxy ? smart_proxy.url : SETTINGS[:katello][:pulp][:url])
370
- scheme = force_http ? 'http' : 'https'
371
- if docker?
372
- "#{pulp_uri.host.downcase}/#{container_repository_name}"
373
- elsif ostree?
374
- "#{scheme}://#{pulp_uri.host.downcase}/pulp/content/web/#{relative_path}"
375
- elsif ansible_collection?
376
- "#{scheme}://#{pulp_uri.host.downcase}/pulp_ansible/galaxy/#{relative_path}/api"
377
- else
378
- "#{scheme}://#{pulp_uri.host.downcase}/pulp/content/#{relative_path}/"
379
- end
380
- end
381
362
  end
382
363
  end
@@ -5,17 +5,10 @@ module Katello
5
5
  PACKAGES = %w(katello candlepin pulp qpid foreman tfm hammer).freeze
6
6
 
7
7
  class << self
8
- def pulpcore_enabled # for downstream 6.9, remove in 6.10
9
- SETTINGS[:katello][:use_pulp_2_for_content_type].nil? || (!SETTINGS[:katello][:use_pulp_2_for_content_type][:yum] &&
10
- !SETTINGS[:katello][:use_pulp_2_for_content_type][:docker] &&
11
- !SETTINGS[:katello][:use_pulp_2_for_content_type][:file]) ||
12
- system('systemctl is-enabled pulpcore-api.service &>/dev/null')
13
- end
14
-
15
8
  def services(capsule_id = nil)
16
9
  proxy = fetch_proxy(capsule_id)
17
- services = [:candlepin, :candlepin_auth, :foreman_tasks, :katello_events, :candlepin_events, :katello_agent]
18
- services += [:pulp3] if proxy&.pulp3_enabled? && pulpcore_enabled
10
+ services = [:candlepin, :candlepin_auth, :foreman_tasks, :katello_events, :candlepin_events]
11
+ services += [:pulp3] if proxy&.pulp3_enabled?
19
12
  if proxy.nil? || proxy.has_feature?(SmartProxy::PULP_NODE_FEATURE) || proxy.has_feature?(SmartProxy::PULP_FEATURE)
20
13
  services += [:pulp, :pulp_auth]
21
14
  end
@@ -395,6 +395,20 @@ module Katello
395
395
  all_instances
396
396
  end
397
397
 
398
+ def full_path(smart_proxy = nil, force_http = false)
399
+ pulp_uri = URI.parse(smart_proxy ? smart_proxy.url : ::SmartProxy.pulp_primary.url)
400
+ scheme = force_http ? 'http' : 'https'
401
+ if docker?
402
+ "#{pulp_uri.host.downcase}/#{container_repository_name}"
403
+ elsif ostree?
404
+ "#{scheme}://#{pulp_uri.host.downcase}/pulp/content/web/#{relative_path}"
405
+ elsif ansible_collection?
406
+ "#{scheme}://#{pulp_uri.host.downcase}/pulp_ansible/galaxy/#{relative_path}/api"
407
+ else
408
+ "#{scheme}://#{pulp_uri.host.downcase}/pulp/content/#{relative_path}/"
409
+ end
410
+ end
411
+
398
412
  def to_hash(content_source = nil, force_http = false)
399
413
  {id: id, name: label, url: full_path(content_source, force_http)}
400
414
  end
@@ -8,6 +8,10 @@ module Cert
8
8
  File.open(Setting[:ssl_ca_file], 'r').read
9
9
  end
10
10
 
11
+ def self.candlepin_client_ca_cert
12
+ File.read(SETTINGS[:katello][:candlepin][:ca_cert_file])
13
+ end
14
+
11
15
  def self.ssl_client_cert
12
16
  @ssl_client_cert ||= OpenSSL::X509::Certificate.new(File.open(ssl_client_cert_filename, 'r').read)
13
17
  end
@@ -4,6 +4,10 @@ module Katello
4
4
  module Pulp3
5
5
  module Api
6
6
  class ContentGuard < Core
7
+ def default_name
8
+ 'RHSMCertGuard'
9
+ end
10
+
7
11
  def self.client_module
8
12
  PulpCertguardClient
9
13
  end
@@ -20,19 +24,49 @@ module Katello
20
24
  PulpCertguardClient::ContentguardsRhsmApi.new(api_client)
21
25
  end
22
26
 
23
- def create(name = "RHSMCertGuard", ca_certificate = Cert::Certs.ca_cert)
24
- data = PulpCertguardClient::CertguardRHSMCertGuard.new(name: name, ca_certificate: ca_certificate)
27
+ def ca_cert
28
+ Cert::Certs.candlepin_client_ca_cert
29
+ end
30
+
31
+ def refresh
32
+ found = list(name: default_name).results.first
33
+ if found && found.ca_certificate != ca_cert
34
+ partial_update(found.pulp_href)
35
+ else
36
+ found = create
37
+ end
38
+ persist_if_needed(found.pulp_href)
39
+ end
40
+
41
+ def persist_if_needed(href)
42
+ return if self.smart_proxy.pulp_mirror?
43
+ Katello::Util::Support.active_record_retry do
44
+ found = Katello::Pulp3::ContentGuard.find_by(:name => default_name)
45
+ if found
46
+ found.update(pulp_href: href)
47
+ else
48
+ Katello::Pulp3::ContentGuard.create(name: default_name, pulp_href: href)
49
+ end
50
+ end
51
+ end
52
+
53
+ def create(name = default_name)
54
+ data = PulpCertguardClient::CertguardRHSMCertGuard.new(name: name, ca_certificate: ca_cert)
25
55
  rhsm_api_client.create(data)
26
56
  rescue self.class.api_exception_class => e
27
- raise e unless list&.results&.first
57
+ if (found = list&.results&.first) #check for possible race condition
58
+ found
59
+ else
60
+ raise e
61
+ end
28
62
  end
29
63
 
30
64
  def list(options = {})
31
65
  rhsm_api_client.list options
32
66
  end
33
67
 
34
- def partial_update(href, ca_certificate = Cert::Certs.ca_cert)
35
- data = PulpCertguardClient::CertguardRHSMCertGuard.new(ca_certificate: ca_certificate)
68
+ def partial_update(href)
69
+ data = PulpCertguardClient::CertguardRHSMCertGuard.new(ca_certificate: ca_cert)
36
70
  rhsm_api_client.partial_update(href, data)
37
71
  end
38
72
 
@@ -5,6 +5,7 @@ module Katello
5
5
  class << self
6
6
  def deliver!
7
7
  SmartProxy.unscoped.with_content.each do |proxy|
8
+ percentage = proxy.pulp_disk_usage[0]['percentage']
8
9
  if percentage < 90 && notification_already_exists?(proxy)
9
10
  blueprint.notifications.where(subject: proxy).destroy_all
10
11
  elsif update_notifications(proxy).empty? && percentage > 90
@@ -1,3 +1,3 @@
1
- <% if @smart_proxy.has_feature?(SmartProxy::PULP_NODE_FEATURE) %>
1
+ <% if @smart_proxy.pulp_mirror? %>
2
2
  <%= select_f(f, :download_policy, download_policies, :label, :name, :size => "col-md-8" ) %>
3
3
  <% end %>
@@ -46,7 +46,7 @@ class MoveKatelloFieldsFromHostgroups < ActiveRecord::Migration[6.0]
46
46
  content_facet.kickstart_repository_id = kickstart_repository_id
47
47
  content_facet.content_view_id = content_view_id
48
48
  content_facet.lifecycle_environment_id = lifecycle_environment_id
49
- unless content_facet.save
49
+ unless content_facet.save(validate: false)
50
50
  Rails.logger.warn("Unable to save content facet hostgroup for #{content_facet.inspect} ")
51
51
  Rails.logger.warn(content_facet.errors.full_messages.join("\n"))
52
52
  end
@@ -24,29 +24,32 @@ class DeletePuppetAndOstreeRepos < ActiveRecord::Migration[6.0]
24
24
  self.table_name = 'katello_ostree_branches'
25
25
  end
26
26
 
27
+ def puppet_repositories
28
+ puppet_query = "SELECT \"katello_repositories\".* FROM \"katello_repositories\"" \
29
+ " INNER JOIN \"katello_root_repositories\" ON \"katello_root_repositories\".\"id\" =" \
30
+ " \"katello_repositories\".\"root_id\" WHERE \"katello_root_repositories\".\"content_type\" = 'puppet'"
31
+ ::Katello::Repository.find_by_sql(puppet_query)
32
+ end
33
+
27
34
  def up
28
- if Katello::Repository.ostree_type.any? || Katello::Repository.puppet_type.any?
29
- User.as_anonymous_admin do
30
- FakeContentViewPuppetModule.delete_all
31
- FakeContentViewPuppetEnvironmentPuppetModule.delete_all
32
- FakeRepositoryPuppetModule.delete_all
33
-
34
- FakeContentViewPuppetEnvironment.delete_all
35
- FakePuppetModule.delete_all
36
-
37
- Katello::Repository.puppet_type.delete_all
38
-
39
- FakeRepositoryOstreeBranch.delete_all
40
- FakeOstreeBranch.delete_all
41
- Katello::Repository.ostree_type.where.not(:library_instance_id => nil, :environment_id => nil).destroy_all #CV LCE repos
42
- Katello::Repository.ostree_type.where.not(:library_instance_id => nil).destroy_all # archive repos
43
- Katello::Repository.ostree_type.destroy_all #all the rest (should just be library repos)
44
-
45
- Katello::ContentViewVersion.where.not(:content_counts => nil).each do |version|
46
- version.content_counts.except!('ostree', 'puppet_module')
47
- version.save
48
- end
49
- end
35
+ FakeContentViewPuppetModule.delete_all
36
+ FakeContentViewPuppetEnvironmentPuppetModule.delete_all
37
+ FakeRepositoryPuppetModule.delete_all
38
+
39
+ FakeContentViewPuppetEnvironment.delete_all
40
+ FakePuppetModule.delete_all
41
+
42
+ ::Katello::Repository.delete(puppet_repositories) if puppet_repositories.any?
43
+
44
+ FakeRepositoryOstreeBranch.delete_all
45
+ FakeOstreeBranch.delete_all
46
+ Katello::Repository.ostree_type.where.not(:library_instance_id => nil, :environment_id => nil).destroy_all #CV LCE repos
47
+ Katello::Repository.ostree_type.where.not(:library_instance_id => nil).destroy_all # archive repos
48
+ Katello::Repository.ostree_type.destroy_all #all the rest (should just be library repos)
49
+
50
+ Katello::ContentViewVersion.where.not(:content_counts => nil).each do |version|
51
+ version.content_counts.except!('ostree', 'puppet_module')
52
+ version.save
50
53
  end
51
54
  end
52
55
 
@@ -0,0 +1,11 @@
1
+ class FixRedHatRootRepositoryArch < ActiveRecord::Migration[6.0]
2
+ def up
3
+ ::Katello::RootRepository.
4
+ joins("INNER JOIN katello_contents ON katello_contents.cp_content_id = katello_root_repositories.content_id").
5
+ where.not(arch: 'noarch').where.not("katello_contents.content_url ILIKE '%$basearch%'").update(arch: 'noarch')
6
+ end
7
+
8
+ def down
9
+ fail ActiveRecord::IrreversibleMigration
10
+ end
11
+ end
data/lib/katello.rb CHANGED
@@ -28,6 +28,6 @@ module Katello
28
28
  require "katello/engine"
29
29
 
30
30
  def self.pulp_server
31
- Katello::Pulp::Server.config(SETTINGS[:katello][:pulp][:url], User.remote_user)
31
+ Katello::Pulp::Server.config(::SmartProxy.pulp_primary.url + '/pulp/api/v2/', User.remote_user)
32
32
  end
33
33
  end
@@ -34,7 +34,6 @@ module Katello
34
34
  },
35
35
  :pulp => {
36
36
  :default_login => 'admin',
37
- :url => 'https://localhost/pulp/api/v2/',
38
37
  :bulk_load_size => 2000,
39
38
  :skip_checksum_validation => false,
40
39
  :upload_chunk_size => 1_048_575, # upload size in bytes to pulp. see SSLRenegBufferSize in apache
@@ -0,0 +1,8 @@
1
+ namespace :katello do
2
+ desc "This task collates hostgroup content facts that were missed during the upgrade from audit.\
3
+ It then updates the hostgroup content_facet accordingly."
4
+ task :fix_hostgroup_facets => :environment do
5
+ User.current = User.anonymous_admin
6
+ ::Katello::Util::HostgroupFacetsHelper.new.main
7
+ end
8
+ end
@@ -1,3 +1,3 @@
1
1
  module Katello
2
- VERSION = "4.0.1".freeze
2
+ VERSION = "4.0.2.1".freeze
3
3
  end
@@ -1,21 +1,32 @@
1
1
  module ProxyAPI
2
2
  class ContainerGateway < ::ProxyAPI::Resource
3
3
  def initialize(args)
4
- @url = args[:url] + "/container_gateway/v2"
4
+ @url = args[:url] + "/container_gateway"
5
5
  super args
6
6
  end
7
7
 
8
- def unauthenticated_repository_list(args = {})
9
- # get '/v2/unauthenticated_repository_list/?'
10
- # put '/v2/unauthenticated_repository_list/?'
11
- @url += "/unauthenticated_repository_list"
12
- if args.empty?
13
- @unauthenticated_repo_list = parse get
14
- else
15
- parse put(args)
16
- end
8
+ def repository_list(args)
9
+ # put '/v2/repository_list/?'
10
+ @url += "/repository_list"
11
+ parse put(args)
17
12
  rescue => e
18
- raise ::ProxyAPI::ProxyException.new(url, e, N_("Unable to perform unauthenticated repository list operation"))
13
+ raise ::ProxyAPI::ProxyException.new(url, e, N_("Unable to update the repository list"))
14
+ end
15
+
16
+ def user_repository_mapping(args)
17
+ # put '/v2/user_repository_mapping/?'
18
+ @url += "/user_repository_mapping"
19
+ parse put(args)
20
+ rescue => e
21
+ raise ::ProxyAPI::ProxyException.new(url, e, N_("Unable to update the user-repository mapping"))
22
+ end
23
+
24
+ def users
25
+ # get '/v2/users/?'
26
+ @url += "/users"
27
+ @users = parse get
28
+ rescue => e
29
+ raise ::ProxyAPI::ProxyException.new(url, e, N_("Unable to get users"))
19
30
  end
20
31
  end
21
32
  end
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.0.1
4
+ version: 4.0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - N/A
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-18 00:00:00.000000000 Z
11
+ date: 2021-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -84,16 +84,30 @@ dependencies:
84
84
  name: foreman-tasks
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: '4.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '4.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: foreman-tasks-core
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "<="
102
+ - !ruby/object:Gem::Version
103
+ version: 0.3.5
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "<="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.5
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: foreman_remote_execution
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -140,16 +154,16 @@ dependencies:
140
154
  name: qpid_proton
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
- - - "<"
157
+ - - ">="
144
158
  - !ruby/object:Gem::Version
145
- version: '0.34'
159
+ version: '0'
146
160
  type: :runtime
147
161
  prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
- - - "<"
164
+ - - ">="
151
165
  - !ruby/object:Gem::Version
152
- version: '0.34'
166
+ version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: stomp
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -1075,7 +1089,6 @@ files:
1075
1089
  - app/lib/actions/pulp3/abstract.rb
1076
1090
  - app/lib/actions/pulp3/abstract_async_task.rb
1077
1091
  - app/lib/actions/pulp3/capsule_content/generate_metadata.rb
1078
- - app/lib/actions/pulp3/capsule_content/refresh_content_guard.rb
1079
1092
  - app/lib/actions/pulp3/capsule_content/refresh_distribution.rb
1080
1093
  - app/lib/actions/pulp3/capsule_content/sync.rb
1081
1094
  - app/lib/actions/pulp3/content_guard/refresh.rb
@@ -1205,6 +1218,7 @@ files:
1205
1218
  - app/lib/katello/util/docker_manifest_clause_generator.rb
1206
1219
  - app/lib/katello/util/errata.rb
1207
1220
  - app/lib/katello/util/filter_clause_generator.rb
1221
+ - app/lib/katello/util/hostgroup_facets_helper.rb
1208
1222
  - app/lib/katello/util/http_proxy.rb
1209
1223
  - app/lib/katello/util/model.rb
1210
1224
  - app/lib/katello/util/module_stream_clause_generator.rb
@@ -2166,6 +2180,7 @@ files:
2166
2180
  - db/migrate/20210201163238_migrate_background_download_policy_to_migrate.rb
2167
2181
  - db/migrate/20210208213920_add_available_module_stream_context.rb
2168
2182
  - db/migrate/20210218214048_change_default_content_view_version_export_history.rb
2183
+ - db/migrate/20210512192745_fix_red_hat_root_repository_arch.rb
2169
2184
  - db/seeds.d/101-locations.rb
2170
2185
  - db/seeds.d/102-organizations.rb
2171
2186
  - db/seeds.d/104-proxy.rb
@@ -4520,6 +4535,7 @@ files:
4520
4535
  - lib/katello/tasks/clean_old_file_repos.rake
4521
4536
  - lib/katello/tasks/clean_published_repo_directories.rake
4522
4537
  - lib/katello/tasks/delete_orphaned_content.rake
4538
+ - lib/katello/tasks/fix_hostgroup_facets.rake
4523
4539
  - lib/katello/tasks/import_applicability.rake
4524
4540
  - lib/katello/tasks/import_subscriptions.rake
4525
4541
  - lib/katello/tasks/jenkins.rake
@@ -1,17 +0,0 @@
1
- module Actions
2
- module Pulp3
3
- module CapsuleContent
4
- class RefreshContentGuard < Pulp3::AbstractAsyncTask
5
- def plan(smart_proxy, options = {})
6
- content_guard_api = ::Katello::Pulp3::Api::ContentGuard.new(smart_proxy)
7
- content_guard_href = content_guard_api.list&.results&.first&.pulp_href
8
- if content_guard_href && options.try(:[], :update)
9
- content_guard_api.partial_update content_guard_href
10
- else
11
- content_guard_api.create
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end