katello 4.13.0.rc1 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/app/controllers/katello/api/registry/registry_proxies_controller.rb +334 -23
 - data/app/controllers/katello/api/v2/repositories_controller.rb +1 -1
 - data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
 - data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
 - data/app/lib/actions/katello/repository/create.rb +17 -11
 - data/app/lib/actions/katello/repository/create_root.rb +4 -2
 - data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
 - data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
 - data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
 - data/app/models/katello/content_view.rb +2 -0
 - data/app/models/katello/glue/pulp/repos.rb +8 -1
 - data/app/models/katello/repository.rb +5 -1
 - data/app/models/katello/root_repository.rb +4 -2
 - data/app/models/katello/trace_status.rb +1 -1
 - data/app/services/katello/pulp3/api/core.rb +8 -0
 - data/app/services/katello/pulp3/api/docker.rb +4 -0
 - data/app/services/katello/pulp3/repository/yum.rb +1 -6
 - data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
 - data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
 - data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +10 -0
 - data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
 - data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +0 -3
 - data/lib/katello/plugin.rb +12 -0
 - data/lib/katello/version.rb +1 -1
 - data/package.json +0 -1
 - data/webpack/components/Content/ContentTable.js +0 -1
 - data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
 - data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
 - data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
 - data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
 - data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +4 -2
 - metadata +7 -7
 - data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
 - data/webpack/utils/paramsFromHash.js +0 -16
 - data/webpack/utils/useUrlParams.js +0 -14
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 24a3d52ddfd156ad5a11c37f63def11242d86b6c67f7e7b5e97cbad375ec0ffa
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: f307cdc997b68aac82c1f7a9b5d9e380eb9c8a93dcacd7e87a3a05dd6c5589c5
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: c188709e872b9e152963f860462b267a480533e7634d5804708838df3701cade9ec0dc8d9c0bc72423bccd93383947a7b1d5a4a624a03245a4b8784c9c73a36e
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 3caf9ffe2eda0fb1f1f3593f1e2badcc92946a483c16f448421193faf067960b73380c81bc2349a6c9bef4b64550ee6410afbf926157b0d132533897614dfc6d
         
     | 
| 
         @@ -3,15 +3,14 @@ module Katello 
     | 
|
| 
       3 
3 
     | 
    
         
             
              class Api::Registry::RegistryProxiesController < Api::V2::ApiController
         
     | 
| 
       4 
4 
     | 
    
         
             
                before_action :disable_strong_params
         
     | 
| 
       5 
5 
     | 
    
         
             
                before_action :confirm_settings
         
     | 
| 
       6 
     | 
    
         
            -
                before_action :confirm_push_settings, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
         
     | 
| 
       7 
     | 
    
         
            -
                                                             :push_manifest]
         
     | 
| 
       8 
6 
     | 
    
         
             
                skip_before_action :authorize
         
     | 
| 
       9 
7 
     | 
    
         
             
                before_action :optional_authorize, only: [:token, :catalog]
         
     | 
| 
       10 
8 
     | 
    
         
             
                before_action :registry_authorize, except: [:token, :v1_search, :catalog]
         
     | 
| 
       11 
9 
     | 
    
         
             
                before_action :authorize_repository_read, only: [:pull_manifest, :tags_list]
         
     | 
| 
       12 
     | 
    
         
            -
                # TODO: authorize_repository_write commented out  
     | 
| 
       13 
     | 
    
         
            -
                # before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
         
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
      
 10 
     | 
    
         
            +
                # TODO: authorize_repository_write commented out due to container push changes. Additional task needed to fix.
         
     | 
| 
      
 11 
     | 
    
         
            +
                # before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
         
     | 
| 
      
 12 
     | 
    
         
            +
                before_action :container_push_prop_validation, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
         
     | 
| 
      
 13 
     | 
    
         
            +
                before_action :create_container_repo_if_needed, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest]
         
     | 
| 
       15 
14 
     | 
    
         
             
                skip_before_action :check_media_type, only: [:start_upload_blob, :upload_blob, :finish_upload_blob,
         
     | 
| 
       16 
15 
     | 
    
         
             
                                                             :push_manifest]
         
     | 
| 
       17 
16 
     | 
    
         | 
| 
         @@ -85,6 +84,301 @@ module Katello 
     | 
|
| 
       85 
84 
     | 
    
         
             
                  return false
         
     | 
| 
       86 
85 
     | 
    
         
             
                end
         
     | 
| 
       87 
86 
     | 
    
         | 
| 
      
 87 
     | 
    
         
            +
                def container_push_prop_validation(props = nil)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  # Handle validation and repo creation for container pushes before talking to pulp
         
     | 
| 
      
 89 
     | 
    
         
            +
                  return false unless confirm_push_settings
         
     | 
| 
      
 90 
     | 
    
         
            +
                  props = parse_blob_push_props if props.nil?
         
     | 
| 
      
 91 
     | 
    
         
            +
                  return false unless check_blob_push_field_syntax(props)
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  # validate input and find the org and product either using downcase label or id
         
     | 
| 
      
 94 
     | 
    
         
            +
                  if props[:schema] == "label"
         
     | 
| 
      
 95 
     | 
    
         
            +
                    return false unless check_blob_push_org_label(props)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    return false unless check_blob_push_product_label(props)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  else
         
     | 
| 
      
 98 
     | 
    
         
            +
                    return false unless check_blob_push_org_id(props)
         
     | 
| 
      
 99 
     | 
    
         
            +
                    return false unless check_blob_push_product_id(props)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  return false unless check_blob_push_container(props)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  true
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                def parse_blob_push_props(path_string = nil)
         
     | 
| 
      
 107 
     | 
    
         
            +
                  # path string should follow one of these formats:
         
     | 
| 
      
 108 
     | 
    
         
            +
                  #   - /v2/{org_label}/{product_label}/{name}/blobs/uploads...
         
     | 
| 
      
 109 
     | 
    
         
            +
                  #   - /v2/id/{org_id}/{product_id}/{name}/blobs/uploads...
         
     | 
| 
      
 110 
     | 
    
         
            +
                  #   - /v2/{org_label}/{product_label}/{name}/manifests/...
         
     | 
| 
      
 111 
     | 
    
         
            +
                  #   - /v2/id/{org_id}/{product_id}/{name}/manifests/...
         
     | 
| 
      
 112 
     | 
    
         
            +
                  # inputs not matching format will return {valid_format: false}
         
     | 
| 
      
 113 
     | 
    
         
            +
                  path_string = @_request.fullpath if path_string.nil?
         
     | 
| 
      
 114 
     | 
    
         
            +
                  segments = path_string.split('/')
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                  if segments.length >= 7 && segments[0] == "" && segments[1] == "v2" &&
         
     | 
| 
      
 117 
     | 
    
         
            +
                    segments[2] != "id" && (segments[5] == "blobs" || segments[5] == "manifests")
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 120 
     | 
    
         
            +
                      valid_format: true,
         
     | 
| 
      
 121 
     | 
    
         
            +
                      schema: "label",
         
     | 
| 
      
 122 
     | 
    
         
            +
                      organization: segments[2],
         
     | 
| 
      
 123 
     | 
    
         
            +
                      product: segments[3],
         
     | 
| 
      
 124 
     | 
    
         
            +
                      name: segments[4]
         
     | 
| 
      
 125 
     | 
    
         
            +
                    }
         
     | 
| 
      
 126 
     | 
    
         
            +
                  elsif segments.length >= 8 && segments[0] == "" && segments[1] == "v2" &&
         
     | 
| 
      
 127 
     | 
    
         
            +
                    segments[2] == "id" && (segments[6] == "blobs" || segments[6] == "manifests")
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                    return {
         
     | 
| 
      
 130 
     | 
    
         
            +
                      valid_format: true,
         
     | 
| 
      
 131 
     | 
    
         
            +
                      schema: "id",
         
     | 
| 
      
 132 
     | 
    
         
            +
                      organization: segments[3],
         
     | 
| 
      
 133 
     | 
    
         
            +
                      product: segments[4],
         
     | 
| 
      
 134 
     | 
    
         
            +
                      name: segments[5]
         
     | 
| 
      
 135 
     | 
    
         
            +
                    }
         
     | 
| 
      
 136 
     | 
    
         
            +
                  else
         
     | 
| 
      
 137 
     | 
    
         
            +
                    return {valid_format: false}
         
     | 
| 
      
 138 
     | 
    
         
            +
                  end
         
     | 
| 
      
 139 
     | 
    
         
            +
                end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                def check_blob_push_field_syntax(props)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  # check basic url field syntax
         
     | 
| 
      
 143 
     | 
    
         
            +
                  unless props[:valid_format]
         
     | 
| 
      
 144 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 145 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 146 
     | 
    
         
            +
                      "Invalid format. Container pushes should follow 'organization_label/product_label/name' OR 'id/organization_id/product_id/name' schema.",
         
     | 
| 
      
 147 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 148 
     | 
    
         
            +
                    )
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
                  return true
         
     | 
| 
      
 151 
     | 
    
         
            +
                end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 154 
     | 
    
         
            +
                def check_blob_push_org_label(props)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  org_label = props[:organization]
         
     | 
| 
      
 156 
     | 
    
         
            +
                  unless org_label.present? && org_label.length > 0
         
     | 
| 
      
 157 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 158 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 159 
     | 
    
         
            +
                      "Invalid format. Organization label cannot be blank.",
         
     | 
| 
      
 160 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 161 
     | 
    
         
            +
                    )
         
     | 
| 
      
 162 
     | 
    
         
            +
                  end
         
     | 
| 
      
 163 
     | 
    
         
            +
                  org = Organization.where("LOWER(label) = '#{org_label}'") # convert to lowercase
         
     | 
| 
      
 164 
     | 
    
         
            +
                  # reject ambiguous orgs (possible due to lowercase conversion)
         
     | 
| 
      
 165 
     | 
    
         
            +
                  if org.length > 1
         
     | 
| 
      
 166 
     | 
    
         
            +
                    # Determine if the repo already exists in one of the possible products. If yes,
         
     | 
| 
      
 167 
     | 
    
         
            +
                    # inform the user they need to destroy the existing repo and use the ID format
         
     | 
| 
      
 168 
     | 
    
         
            +
                    unless props[:product].blank? || props[:name].blank?
         
     | 
| 
      
 169 
     | 
    
         
            +
                      org.each do |o|
         
     | 
| 
      
 170 
     | 
    
         
            +
                        products = get_matching_products_from_org(o, props[:product])
         
     | 
| 
      
 171 
     | 
    
         
            +
                        products.each do |prod|
         
     | 
| 
      
 172 
     | 
    
         
            +
                          root_repos = get_root_repo_from_product(prod, props[:name])
         
     | 
| 
      
 173 
     | 
    
         
            +
                          unless root_repos.empty?
         
     | 
| 
      
 174 
     | 
    
         
            +
                            return render_podman_error(
         
     | 
| 
      
 175 
     | 
    
         
            +
                              "NAME_INVALID",
         
     | 
| 
      
 176 
     | 
    
         
            +
                              "Due to a change in your organizations, this container name has become "\
         
     | 
| 
      
 177 
     | 
    
         
            +
                                "ambiguous (org name '#{org_label}'). If you wish to continue using this "\
         
     | 
| 
      
 178 
     | 
    
         
            +
                                "container name, destroy the organization in conflict with '#{o.name} (id "\
         
     | 
| 
      
 179 
     | 
    
         
            +
                                "#{o.id}). If you wish to keep both orgs, destroy '#{o.label}/#{prod.label}/"\
         
     | 
| 
      
 180 
     | 
    
         
            +
                                "#{root_repos.first.label}' and retry your push using the id format.",
         
     | 
| 
      
 181 
     | 
    
         
            +
                              :conflict
         
     | 
| 
      
 182 
     | 
    
         
            +
                            )
         
     | 
| 
      
 183 
     | 
    
         
            +
                          end
         
     | 
| 
      
 184 
     | 
    
         
            +
                        end
         
     | 
| 
      
 185 
     | 
    
         
            +
                      end
         
     | 
| 
      
 186 
     | 
    
         
            +
                    end
         
     | 
| 
      
 187 
     | 
    
         
            +
             
     | 
| 
      
 188 
     | 
    
         
            +
                    # Otherwise tell them to try pushing with ID format
         
     | 
| 
      
 189 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 190 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 191 
     | 
    
         
            +
                      "Organization label '#{org_label}' is ambiguous. Try using an id-based container name.",
         
     | 
| 
      
 192 
     | 
    
         
            +
                      :conflict
         
     | 
| 
      
 193 
     | 
    
         
            +
                    )
         
     | 
| 
      
 194 
     | 
    
         
            +
                  end
         
     | 
| 
      
 195 
     | 
    
         
            +
                  if org.length == 0
         
     | 
| 
      
 196 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 197 
     | 
    
         
            +
                      "NAME_UNKNOWN",
         
     | 
| 
      
 198 
     | 
    
         
            +
                      "Organization not found: '#{org_label}'",
         
     | 
| 
      
 199 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 200 
     | 
    
         
            +
                    )
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
                  @organization = org.first
         
     | 
| 
      
 203 
     | 
    
         
            +
                  true
         
     | 
| 
      
 204 
     | 
    
         
            +
                end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                def check_blob_push_org_id(props)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  org_id = props[:organization]
         
     | 
| 
      
 208 
     | 
    
         
            +
                  unless org_id.present? && org_id == org_id.to_i.to_s
         
     | 
| 
      
 209 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 210 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 211 
     | 
    
         
            +
                      "Invalid format. Organization id must be an integer without leading zeros.",
         
     | 
| 
      
 212 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 213 
     | 
    
         
            +
                    )
         
     | 
| 
      
 214 
     | 
    
         
            +
                  end
         
     | 
| 
      
 215 
     | 
    
         
            +
                  @organization = Organization.find_by_id(org_id.to_i)
         
     | 
| 
      
 216 
     | 
    
         
            +
                  if @organization.nil?
         
     | 
| 
      
 217 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 218 
     | 
    
         
            +
                      "NAME_UNKNOWN",
         
     | 
| 
      
 219 
     | 
    
         
            +
                      "Organization id not found: '#{org_id}'",
         
     | 
| 
      
 220 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 221 
     | 
    
         
            +
                    )
         
     | 
| 
      
 222 
     | 
    
         
            +
                  end
         
     | 
| 
      
 223 
     | 
    
         
            +
                  true
         
     | 
| 
      
 224 
     | 
    
         
            +
                end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                def check_blob_push_product_label(props)
         
     | 
| 
      
 227 
     | 
    
         
            +
                  prod_label = props[:product]
         
     | 
| 
      
 228 
     | 
    
         
            +
                  unless prod_label.present? && prod_label.length > 0
         
     | 
| 
      
 229 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 230 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 231 
     | 
    
         
            +
                      "Invalid format. Product label cannot be blank.",
         
     | 
| 
      
 232 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 233 
     | 
    
         
            +
                    )
         
     | 
| 
      
 234 
     | 
    
         
            +
                  end
         
     | 
| 
      
 235 
     | 
    
         
            +
                  product = get_matching_products_from_org(@organization, prod_label)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  # reject ambiguous products (possible due to lowercase conversion)
         
     | 
| 
      
 237 
     | 
    
         
            +
                  if product.length > 1
         
     | 
| 
      
 238 
     | 
    
         
            +
                    # Determine if the repo already exists in one of the possible products. If yes,
         
     | 
| 
      
 239 
     | 
    
         
            +
                    # inform the user they need to destroy the existing repo and use the ID format
         
     | 
| 
      
 240 
     | 
    
         
            +
                    unless props[:name].blank?
         
     | 
| 
      
 241 
     | 
    
         
            +
                      product.each do |prod|
         
     | 
| 
      
 242 
     | 
    
         
            +
                        root_repos = get_root_repo_from_product(prod, props[:name])
         
     | 
| 
      
 243 
     | 
    
         
            +
                        unless root_repos.empty?
         
     | 
| 
      
 244 
     | 
    
         
            +
                          return render_podman_error(
         
     | 
| 
      
 245 
     | 
    
         
            +
                            "NAME_INVALID",
         
     | 
| 
      
 246 
     | 
    
         
            +
                            "Due to a change in your products, this container name has become ambiguous "\
         
     | 
| 
      
 247 
     | 
    
         
            +
                              "(product name '#{prod_label}'). If you wish to continue using this container "\
         
     | 
| 
      
 248 
     | 
    
         
            +
                              "name, destroy the product in conflict with '#{prod.name}' (id #{prod.id}). If "\
         
     | 
| 
      
 249 
     | 
    
         
            +
                              "you wish to keep both products, destroy '#{@organization.label}/#{prod.label}/"\
         
     | 
| 
      
 250 
     | 
    
         
            +
                              "#{root_repos.first.label}' and retry your push using the id format.",
         
     | 
| 
      
 251 
     | 
    
         
            +
                            :conflict
         
     | 
| 
      
 252 
     | 
    
         
            +
                          )
         
     | 
| 
      
 253 
     | 
    
         
            +
                        end
         
     | 
| 
      
 254 
     | 
    
         
            +
                      end
         
     | 
| 
      
 255 
     | 
    
         
            +
                    end
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 258 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 259 
     | 
    
         
            +
                      "Product label '#{prod_label}' is ambiguous. Try using an id-based container name.",
         
     | 
| 
      
 260 
     | 
    
         
            +
                      :conflict
         
     | 
| 
      
 261 
     | 
    
         
            +
                    )
         
     | 
| 
      
 262 
     | 
    
         
            +
                  end
         
     | 
| 
      
 263 
     | 
    
         
            +
                  if product.length == 0
         
     | 
| 
      
 264 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 265 
     | 
    
         
            +
                      "NAME_UNKNOWN",
         
     | 
| 
      
 266 
     | 
    
         
            +
                      "Product not found: '#{prod_label}'",
         
     | 
| 
      
 267 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 268 
     | 
    
         
            +
                    )
         
     | 
| 
      
 269 
     | 
    
         
            +
                  end
         
     | 
| 
      
 270 
     | 
    
         
            +
                  @product = product.first
         
     | 
| 
      
 271 
     | 
    
         
            +
                  true
         
     | 
| 
      
 272 
     | 
    
         
            +
                end
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                def check_blob_push_product_id(props)
         
     | 
| 
      
 275 
     | 
    
         
            +
                  prod_id = props[:product]
         
     | 
| 
      
 276 
     | 
    
         
            +
                  unless prod_id.present? && prod_id == prod_id.to_i.to_s
         
     | 
| 
      
 277 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 278 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 279 
     | 
    
         
            +
                      "Invalid format. Product id must be an integer without leading zeros.",
         
     | 
| 
      
 280 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 281 
     | 
    
         
            +
                    )
         
     | 
| 
      
 282 
     | 
    
         
            +
                  end
         
     | 
| 
      
 283 
     | 
    
         
            +
                  @product = @organization.products.find_by_id(prod_id.to_i)
         
     | 
| 
      
 284 
     | 
    
         
            +
                  if @product.nil?
         
     | 
| 
      
 285 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 286 
     | 
    
         
            +
                      "NAME_UNKNOWN",
         
     | 
| 
      
 287 
     | 
    
         
            +
                      "Product id not found: '#{prod_id}'",
         
     | 
| 
      
 288 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 289 
     | 
    
         
            +
                    )
         
     | 
| 
      
 290 
     | 
    
         
            +
                  end
         
     | 
| 
      
 291 
     | 
    
         
            +
                  true
         
     | 
| 
      
 292 
     | 
    
         
            +
                end
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                def get_matching_products_from_org(organization, product_label)
         
     | 
| 
      
 295 
     | 
    
         
            +
                  return organization.products.where("LOWER(label) = '#{product_label}'") # convert to lowercase
         
     | 
| 
      
 296 
     | 
    
         
            +
                end
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                def get_root_repo_from_product(product, root_repo_name)
         
     | 
| 
      
 299 
     | 
    
         
            +
                  return product.root_repositories.where(label: root_repo_name)
         
     | 
| 
      
 300 
     | 
    
         
            +
                end
         
     | 
| 
      
 301 
     | 
    
         
            +
             
     | 
| 
      
 302 
     | 
    
         
            +
                def check_blob_push_container(props)
         
     | 
| 
      
 303 
     | 
    
         
            +
                  unless props[:name].present? && props[:name].length > 0
         
     | 
| 
      
 304 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 305 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 306 
     | 
    
         
            +
                      "Invalid format. Container name cannot be blank.",
         
     | 
| 
      
 307 
     | 
    
         
            +
                      :bad_request
         
     | 
| 
      
 308 
     | 
    
         
            +
                    )
         
     | 
| 
      
 309 
     | 
    
         
            +
                  end
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
      
 311 
     | 
    
         
            +
                  @container_name = props[:name]
         
     | 
| 
      
 312 
     | 
    
         
            +
                  @container_push_name_format = props[:schema]
         
     | 
| 
      
 313 
     | 
    
         
            +
                  if @container_push_name_format == "label"
         
     | 
| 
      
 314 
     | 
    
         
            +
                    @container_path_input = "#{props[:organization]}/#{props[:product]}/#{props[:name]}"
         
     | 
| 
      
 315 
     | 
    
         
            +
                  else
         
     | 
| 
      
 316 
     | 
    
         
            +
                    @container_path_input = "id/#{props[:organization]}/#{props[:product]}/#{props[:name]}"
         
     | 
| 
      
 317 
     | 
    
         
            +
                  end
         
     | 
| 
      
 318 
     | 
    
         
            +
             
     | 
| 
      
 319 
     | 
    
         
            +
                  # If the repo already exists, check if the existing push format matches
         
     | 
| 
      
 320 
     | 
    
         
            +
                  root_repo = get_root_repo_from_product(@product, @container_name).first
         
     | 
| 
      
 321 
     | 
    
         
            +
                  if !root_repo.nil? && @container_push_name_format != root_repo.container_push_name_format
         
     | 
| 
      
 322 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 323 
     | 
    
         
            +
                      "NAME_INVALID",
         
     | 
| 
      
 324 
     | 
    
         
            +
                      "Repository name '#{@container_name}' already exists in this product using a different naming scheme. Please retry your request with the #{root_repo.container_push_name_format} format or destroy and recreate the repository using your preferred schema.",
         
     | 
| 
      
 325 
     | 
    
         
            +
                      :conflict
         
     | 
| 
      
 326 
     | 
    
         
            +
                    )
         
     | 
| 
      
 327 
     | 
    
         
            +
                  end
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                  true
         
     | 
| 
      
 330 
     | 
    
         
            +
                end
         
     | 
| 
      
 331 
     | 
    
         
            +
             
     | 
| 
      
 332 
     | 
    
         
            +
                def create_container_repo_if_needed
         
     | 
| 
      
 333 
     | 
    
         
            +
                  if get_root_repo_from_product(@product, @container_name).empty?
         
     | 
| 
      
 334 
     | 
    
         
            +
                    root = @product.add_repo(
         
     | 
| 
      
 335 
     | 
    
         
            +
                      name: @container_name,
         
     | 
| 
      
 336 
     | 
    
         
            +
                      label: @container_name,
         
     | 
| 
      
 337 
     | 
    
         
            +
                      download_policy: 'immediate',
         
     | 
| 
      
 338 
     | 
    
         
            +
                      content_type: Repository::DOCKER_TYPE,
         
     | 
| 
      
 339 
     | 
    
         
            +
                      unprotected: true,
         
     | 
| 
      
 340 
     | 
    
         
            +
                      is_container_push: true,
         
     | 
| 
      
 341 
     | 
    
         
            +
                      container_push_name: @container_path_input,
         
     | 
| 
      
 342 
     | 
    
         
            +
                      container_push_name_format: @container_push_name_format
         
     | 
| 
      
 343 
     | 
    
         
            +
                    )
         
     | 
| 
      
 344 
     | 
    
         
            +
                    sync_task(::Actions::Katello::Repository::CreateRoot, root, @container_path_input)
         
     | 
| 
      
 345 
     | 
    
         
            +
                  end
         
     | 
| 
      
 346 
     | 
    
         
            +
                end
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
                def blob_push_cleanup
         
     | 
| 
      
 349 
     | 
    
         
            +
                  # after manifest upload, index content and set version href using pulp api
         
     | 
| 
      
 350 
     | 
    
         
            +
                  root_repo = get_root_repo_from_product(@product, @container_name)&.first
         
     | 
| 
      
 351 
     | 
    
         
            +
                  instance_repo = root_repo&.library_instance
         
     | 
| 
      
 352 
     | 
    
         
            +
             
     | 
| 
      
 353 
     | 
    
         
            +
                  unless root_repo.present? && instance_repo.present?
         
     | 
| 
      
 354 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 355 
     | 
    
         
            +
                      "BLOB_UPLOAD_UNKNOWN",
         
     | 
| 
      
 356 
     | 
    
         
            +
                      "Could not locate local uploaded repository for content indexing.",
         
     | 
| 
      
 357 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 358 
     | 
    
         
            +
                    )
         
     | 
| 
      
 359 
     | 
    
         
            +
                  end
         
     | 
| 
      
 360 
     | 
    
         
            +
             
     | 
| 
      
 361 
     | 
    
         
            +
                  api = ::Katello::Pulp3::Repository.api(SmartProxy.pulp_primary, ::Katello::Repository::DOCKER_TYPE).container_push_api
         
     | 
| 
      
 362 
     | 
    
         
            +
                  api_response = api.list(name: @container_path_input)&.results&.first
         
     | 
| 
      
 363 
     | 
    
         
            +
                  latest_version_href = api_response&.latest_version_href
         
     | 
| 
      
 364 
     | 
    
         
            +
                  pulp_href = api_response&.pulp_href
         
     | 
| 
      
 365 
     | 
    
         
            +
             
     | 
| 
      
 366 
     | 
    
         
            +
                  if latest_version_href.empty? || pulp_href.empty?
         
     | 
| 
      
 367 
     | 
    
         
            +
                    return render_podman_error(
         
     | 
| 
      
 368 
     | 
    
         
            +
                      "BLOB_UPLOAD_UNKNOWN",
         
     | 
| 
      
 369 
     | 
    
         
            +
                      "Could not locate repository properties for content indexing.",
         
     | 
| 
      
 370 
     | 
    
         
            +
                      :not_found
         
     | 
| 
      
 371 
     | 
    
         
            +
                    )
         
     | 
| 
      
 372 
     | 
    
         
            +
                  end
         
     | 
| 
      
 373 
     | 
    
         
            +
             
     | 
| 
      
 374 
     | 
    
         
            +
                  instance_repo.update!(version_href: latest_version_href)
         
     | 
| 
      
 375 
     | 
    
         
            +
                  ::Katello::Pulp3::RepositoryReference.where(root_repository_id: instance_repo.root_id,
         
     | 
| 
      
 376 
     | 
    
         
            +
                    content_view_id: instance_repo.content_view.id, repository_href: pulp_href).create!
         
     | 
| 
      
 377 
     | 
    
         
            +
                  instance_repo.index_content
         
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
      
 379 
     | 
    
         
            +
                  true
         
     | 
| 
      
 380 
     | 
    
         
            +
                end
         
     | 
| 
      
 381 
     | 
    
         
            +
             
     | 
| 
       88 
382 
     | 
    
         
             
                def find_writable_repository
         
     | 
| 
       89 
383 
     | 
    
         
             
                  Repository.docker_type.syncable.find_by_container_repository_name(params[:repository])
         
     | 
| 
       90 
384 
     | 
    
         
             
                end
         
     | 
| 
         @@ -205,17 +499,6 @@ module Katello 
     | 
|
| 
       205 
499 
     | 
    
         
             
                  redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers, max_redirects: 0) }
         
     | 
| 
       206 
500 
     | 
    
         
             
                end
         
     | 
| 
       207 
501 
     | 
    
         | 
| 
       208 
     | 
    
         
            -
                def push_manifest
         
     | 
| 
       209 
     | 
    
         
            -
                  headers = translated_headers_for_proxy
         
     | 
| 
       210 
     | 
    
         
            -
                  headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
         
     | 
| 
       211 
     | 
    
         
            -
                  body = @_request.body.read
         
     | 
| 
       212 
     | 
    
         
            -
                  pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, body, headers)
         
     | 
| 
       213 
     | 
    
         
            -
                  pulp_response.headers.each do |key, value|
         
     | 
| 
       214 
     | 
    
         
            -
                    response.header[key.to_s] = value
         
     | 
| 
       215 
     | 
    
         
            -
                  end
         
     | 
| 
       216 
     | 
    
         
            -
                  head pulp_response.code
         
     | 
| 
       217 
     | 
    
         
            -
                end
         
     | 
| 
       218 
     | 
    
         
            -
             
     | 
| 
       219 
502 
     | 
    
         
             
                def start_upload_blob
         
     | 
| 
       220 
503 
     | 
    
         
             
                  headers = translated_headers_for_proxy
         
     | 
| 
       221 
504 
     | 
    
         
             
                  headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
         
     | 
| 
         @@ -225,6 +508,7 @@ module Katello 
     | 
|
| 
       225 
508 
     | 
    
         
             
                  pulp_response.headers.each do |key, value|
         
     | 
| 
       226 
509 
     | 
    
         
             
                    response.header[key.to_s] = value
         
     | 
| 
       227 
510 
     | 
    
         
             
                  end
         
     | 
| 
      
 511 
     | 
    
         
            +
             
     | 
| 
       228 
512 
     | 
    
         
             
                  head pulp_response.code
         
     | 
| 
       229 
513 
     | 
    
         
             
                end
         
     | 
| 
       230 
514 
     | 
    
         | 
| 
         @@ -268,6 +552,21 @@ module Katello 
     | 
|
| 
       268 
552 
     | 
    
         
             
                  head pulp_response.code
         
     | 
| 
       269 
553 
     | 
    
         
             
                end
         
     | 
| 
       270 
554 
     | 
    
         | 
| 
      
 555 
     | 
    
         
            +
                def push_manifest
         
     | 
| 
      
 556 
     | 
    
         
            +
                  headers = translated_headers_for_proxy
         
     | 
| 
      
 557 
     | 
    
         
            +
                  headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type']
         
     | 
| 
      
 558 
     | 
    
         
            +
                  body = @_request.body.read
         
     | 
| 
      
 559 
     | 
    
         
            +
                  pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, body, headers)
         
     | 
| 
      
 560 
     | 
    
         
            +
                  pulp_response.headers.each do |key, value|
         
     | 
| 
      
 561 
     | 
    
         
            +
                    response.header[key.to_s] = value
         
     | 
| 
      
 562 
     | 
    
         
            +
                  end
         
     | 
| 
      
 563 
     | 
    
         
            +
             
     | 
| 
      
 564 
     | 
    
         
            +
                  cleanup_result = blob_push_cleanup if pulp_response.code.between?(200, 299)
         
     | 
| 
      
 565 
     | 
    
         
            +
                  return false unless cleanup_result
         
     | 
| 
      
 566 
     | 
    
         
            +
             
     | 
| 
      
 567 
     | 
    
         
            +
                  head pulp_response.code
         
     | 
| 
      
 568 
     | 
    
         
            +
                end
         
     | 
| 
      
 569 
     | 
    
         
            +
             
     | 
| 
       271 
570 
     | 
    
         
             
                def ping
         
     | 
| 
       272 
571 
     | 
    
         
             
                  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
         
     | 
| 
       273 
572 
     | 
    
         
             
                  render json: {}, status: :ok
         
     | 
| 
         @@ -278,10 +577,10 @@ module Katello 
     | 
|
| 
       278 
577 
     | 
    
         
             
                end
         
     | 
| 
       279 
578 
     | 
    
         | 
| 
       280 
579 
     | 
    
         
             
                def v1_search
         
     | 
| 
       281 
     | 
    
         
            -
                  # Checks for  
     | 
| 
      
 580 
     | 
    
         
            +
                  # Checks for v2 client and issues a 404 in that case. Podman
         
     | 
| 
       282 
581 
     | 
    
         
             
                  # examines the response from a /v1_search request. If the result
         
     | 
| 
       283 
582 
     | 
    
         
             
                  # is a 4XX, it will then proceed with a request to /_catalog
         
     | 
| 
       284 
     | 
    
         
            -
                  if request.headers[' 
     | 
| 
      
 583 
     | 
    
         
            +
                  if request.headers['HTTP_DOCKER_DISTRIBUTION_API_VERSION'] == 'registry/2.0'
         
     | 
| 
       285 
584 
     | 
    
         
             
                    render json: {}, status: :not_found
         
     | 
| 
       286 
585 
     | 
    
         
             
                    return
         
     | 
| 
       287 
586 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -423,8 +722,11 @@ module Katello 
     | 
|
| 
       423 
722 
     | 
    
         | 
| 
       424 
723 
     | 
    
         
             
                def confirm_push_settings
         
     | 
| 
       425 
724 
     | 
    
         
             
                  return true if SETTINGS.dig(:katello, :container_image_registry, :allow_push)
         
     | 
| 
       426 
     | 
    
         
            -
                   
     | 
| 
       427 
     | 
    
         
            -
             
     | 
| 
      
 725 
     | 
    
         
            +
                  render_podman_error(
         
     | 
| 
      
 726 
     | 
    
         
            +
                    "UNSUPPORTED",
         
     | 
| 
      
 727 
     | 
    
         
            +
                    "Registry push is not enabled. To enable, add ':katello:'->':container_image_registry:'->':allow_push: true' in the katello settings file.",
         
     | 
| 
      
 728 
     | 
    
         
            +
                    :unprocessable_entity
         
     | 
| 
      
 729 
     | 
    
         
            +
                  )
         
     | 
| 
       428 
730 
     | 
    
         
             
                end
         
     | 
| 
       429 
731 
     | 
    
         | 
| 
       430 
732 
     | 
    
         
             
                def request_url
         
     | 
| 
         @@ -446,10 +748,19 @@ module Katello 
     | 
|
| 
       446 
748 
     | 
    
         
             
                  Rails.logger.debug "With body: #{filter_sensitive_data(response.body)}\n" unless route_name == 'pull_blob'
         
     | 
| 
       447 
749 
     | 
    
         
             
                end
         
     | 
| 
       448 
750 
     | 
    
         | 
| 
      
 751 
     | 
    
         
            +
                def render_podman_error(code, message, status = :bad_request)
         
     | 
| 
      
 752 
     | 
    
         
            +
                  # Renders a podman-compatible error and returns false.
         
     | 
| 
      
 753 
     | 
    
         
            +
                  #     code: uppercase string code from opencontainer error code spec:
         
     | 
| 
      
 754 
     | 
    
         
            +
                  #         https://specs.opencontainers.org/distribution-spec/?v=v1.0.0#DISTRIBUTION-SPEC-140
         
     | 
| 
      
 755 
     | 
    
         
            +
                  #     message: a custom error string
         
     | 
| 
      
 756 
     | 
    
         
            +
                  #     status: a symbol in the 400 block of the rails response code table:
         
     | 
| 
      
 757 
     | 
    
         
            +
                  #         https://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option
         
     | 
| 
      
 758 
     | 
    
         
            +
                  render json: {errors: [{code: code, message: message}]}, status: status
         
     | 
| 
      
 759 
     | 
    
         
            +
                  false
         
     | 
| 
      
 760 
     | 
    
         
            +
                end
         
     | 
| 
      
 761 
     | 
    
         
            +
             
     | 
| 
       449 
762 
     | 
    
         
             
                def item_not_found(item)
         
     | 
| 
       450 
     | 
    
         
            -
                   
     | 
| 
       451 
     | 
    
         
            -
                  # returning errors based on registry specifications in https://docs.docker.com/registry/spec/api/#errors
         
     | 
| 
       452 
     | 
    
         
            -
                  render json: {errors: [code: :invalid_request, message: msg, details: msg]}, status: :not_found
         
     | 
| 
      
 763 
     | 
    
         
            +
                  render_podman_error("NAME_UNKNOWN", "#{item} was not found!", :not_found)
         
     | 
| 
       453 
764 
     | 
    
         
             
                end
         
     | 
| 
       454 
765 
     | 
    
         
             
              end
         
     | 
| 
       455 
766 
     | 
    
         
             
            end
         
     | 
| 
         @@ -48,7 +48,7 @@ Pass [] to make repo available for clients regardless of OS version. Maximum len 
     | 
|
| 
       48 
48 
     | 
    
         
             
                  param :ssl_client_cert_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Cert"), :allow_nil => true
         
     | 
| 
       49 
49 
     | 
    
         
             
                  param :ssl_client_key_id, :number, :desc => N_("Identifier of the content credential containing the SSL Client Key"), :allow_nil => true
         
     | 
| 
       50 
50 
     | 
    
         
             
                  param :unprotected, :bool, :desc => N_("true if this repository can be published via HTTP")
         
     | 
| 
       51 
     | 
    
         
            -
                  param :checksum_type, String, :desc => N_("Checksum  
     | 
| 
      
 51 
     | 
    
         
            +
                  param :checksum_type, String, :desc => N_("Checksum used for published repository contents. Supported types: %s") % Katello::RootRepository::CHECKSUM_TYPES.join(', ')
         
     | 
| 
       52 
52 
     | 
    
         
             
                  param :docker_upstream_name, String, :desc => N_("Name of the upstream docker repository")
         
     | 
| 
       53 
53 
     | 
    
         
             
                  param :include_tags, Array, :desc => N_("Comma-separated list of tags to sync for a container image repository")
         
     | 
| 
       54 
54 
     | 
    
         
             
                  param :exclude_tags, Array, :desc => N_("Comma-separated list of tags to exclude when syncing a container image repository. Default: any tag ending in \"-source\"")
         
     | 
| 
         @@ -69,8 +69,13 @@ module Actions 
     | 
|
| 
       69 
69 
     | 
    
         
             
                    end
         
     | 
| 
       70 
70 
     | 
    
         | 
| 
       71 
71 
     | 
    
         
             
                    def update_content_counts(_execution_plan)
         
     | 
| 
       72 
     | 
    
         
            -
                       
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
      
 72 
     | 
    
         
            +
                      if Setting[:automatic_content_count_updates]
         
     | 
| 
      
 73 
     | 
    
         
            +
                        smart_proxy = ::SmartProxy.unscoped.find(input[:smart_proxy_id])
         
     | 
| 
      
 74 
     | 
    
         
            +
                        ::ForemanTasks.async_task(::Actions::Katello::CapsuleContent::UpdateContentCounts, smart_proxy)
         
     | 
| 
      
 75 
     | 
    
         
            +
                      else
         
     | 
| 
      
 76 
     | 
    
         
            +
                        Rails.logger.info "Skipping content counts update as automatic content count updates are disabled. To enable automatic content count updates, set the 'automatic_content_count_updates' setting to true.
         
     | 
| 
      
 77 
     | 
    
         
            +
            To update content counts manually, run the 'Update Content Counts' action."
         
     | 
| 
      
 78 
     | 
    
         
            +
                      end
         
     | 
| 
       74 
79 
     | 
    
         
             
                    end
         
     | 
| 
       75 
80 
     | 
    
         | 
| 
       76 
81 
     | 
    
         
             
                    def resource_locks
         
     | 
| 
         @@ -16,10 +16,15 @@ module Actions 
     | 
|
| 
       16 
16 
     | 
    
         
             
                        repositories.each do |repo|
         
     | 
| 
       17 
17 
     | 
    
         
             
                          plan_action(Katello::Repository::RefreshRepository, repo)
         
     | 
| 
       18 
18 
     | 
    
         
             
                        end
         
     | 
| 
       19 
     | 
    
         
            -
                        plan_self
         
     | 
| 
      
 19 
     | 
    
         
            +
                        plan_self(:organization_name => organization.name)
         
     | 
| 
       20 
20 
     | 
    
         
             
                      end
         
     | 
| 
       21 
21 
     | 
    
         
             
                    end
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
                    def run
         
     | 
| 
      
 24 
     | 
    
         
            +
                      organization = ::Organization.find_by(name: input[:organization_name])
         
     | 
| 
      
 25 
     | 
    
         
            +
                      organization&.manifest_expiration_date(cached: false) # update organization.manifest_imported? value
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
       23 
28 
     | 
    
         
             
                    def failure_notification(plan)
         
     | 
| 
       24 
29 
     | 
    
         
             
                      ::Katello::UINotifications::Subscriptions::ManifestDeleteError.deliver!(
         
     | 
| 
       25 
30 
     | 
    
         
             
                        :subject => subject_organization,
         
     | 
| 
         @@ -13,10 +13,13 @@ module Actions 
     | 
|
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                      org = repository.organization
         
     | 
| 
       15 
15 
     | 
    
         
             
                      sequence do
         
     | 
| 
       16 
     | 
    
         
            -
                         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 16 
     | 
    
         
            +
                        # Container push repositories will already be in pulp. The version_href is
         
     | 
| 
      
 17 
     | 
    
         
            +
                        # directly updated after a push.
         
     | 
| 
      
 18 
     | 
    
         
            +
                        unless root.is_container_push
         
     | 
| 
      
 19 
     | 
    
         
            +
                          create_action = plan_action(Pulp3::Orchestration::Repository::Create,
         
     | 
| 
      
 20 
     | 
    
         
            +
                                                      repository, SmartProxy.pulp_primary, force_repo_create)
         
     | 
| 
      
 21 
     | 
    
         
            +
                          return if create_action.error
         
     | 
| 
      
 22 
     | 
    
         
            +
                        end
         
     | 
| 
       20 
23 
     | 
    
         | 
| 
       21 
24 
     | 
    
         
             
                        # when creating a clone, the following actions are handled by the
         
     | 
| 
       22 
25 
     | 
    
         
             
                        # publish/promote process
         
     | 
| 
         @@ -32,13 +35,16 @@ module Actions 
     | 
|
| 
       32 
35 
     | 
    
         
             
                          end
         
     | 
| 
       33 
36 
     | 
    
         
             
                        end
         
     | 
| 
       34 
37 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                           
     | 
| 
       38 
     | 
    
         
            -
                             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                                 
     | 
| 
      
 38 
     | 
    
         
            +
                        # Container push repos do not need metadata generation or ACS (they do not sync)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        unless root.is_container_push
         
     | 
| 
      
 40 
     | 
    
         
            +
                          concurrence do
         
     | 
| 
      
 41 
     | 
    
         
            +
                            plan_self(:repository_id => repository.id, :clone => clone)
         
     | 
| 
      
 42 
     | 
    
         
            +
                            if !clone && repository.url.present?
         
     | 
| 
      
 43 
     | 
    
         
            +
                              repository.product.alternate_content_sources.with_type(repository.content_type).each do |acs|
         
     | 
| 
      
 44 
     | 
    
         
            +
                                acs.smart_proxies.each do |smart_proxy|
         
     | 
| 
      
 45 
     | 
    
         
            +
                                  smart_proxy_acs = ::Katello::SmartProxyAlternateContentSource.create(alternate_content_source_id: acs.id, smart_proxy_id: smart_proxy.id, repository_id: repository.id)
         
     | 
| 
      
 46 
     | 
    
         
            +
                                  plan_action(Pulp3::Orchestration::AlternateContentSource::Create, smart_proxy_acs)
         
     | 
| 
      
 47 
     | 
    
         
            +
                                end
         
     | 
| 
       42 
48 
     | 
    
         
             
                              end
         
     | 
| 
       43 
49 
     | 
    
         
             
                            end
         
     | 
| 
       44 
50 
     | 
    
         
             
                          end
         
     | 
| 
         @@ -2,13 +2,15 @@ module Actions 
     | 
|
| 
       2 
2 
     | 
    
         
             
              module Katello
         
     | 
| 
       3 
3 
     | 
    
         
             
                module Repository
         
     | 
| 
       4 
4 
     | 
    
         
             
                  class CreateRoot < Actions::EntryAction
         
     | 
| 
       5 
     | 
    
         
            -
                    def plan(root)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def plan(root, relative_path = nil)
         
     | 
| 
       6 
6 
     | 
    
         
             
                      root.save!
         
     | 
| 
       7 
7 
     | 
    
         
             
                      repository = ::Katello::Repository.new(:environment => root.organization.library,
         
     | 
| 
       8 
8 
     | 
    
         
             
                                                  :content_view_version => root.organization.library.default_content_view_version,
         
     | 
| 
       9 
9 
     | 
    
         
             
                                                  :root => root)
         
     | 
| 
       10 
     | 
    
         
            -
                      repository. 
     | 
| 
      
 10 
     | 
    
         
            +
                      repository.container_repository_name = relative_path
         
     | 
| 
      
 11 
     | 
    
         
            +
                      repository.relative_path = relative_path || repository.custom_repo_path
         
     | 
| 
       11 
12 
     | 
    
         
             
                      repository.save!
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
       12 
14 
     | 
    
         
             
                      action_subject(repository)
         
     | 
| 
       13 
15 
     | 
    
         
             
                      plan_action(::Actions::Katello::Repository::Create, repository)
         
     | 
| 
       14 
16 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Actions
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Pulp3
         
     | 
| 
      
 3 
     | 
    
         
            +
                module OrphanCleanup
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class PurgeCompletedTasks < Pulp3::AbstractAsyncTask
         
     | 
| 
      
 5 
     | 
    
         
            +
                    def plan(smart_proxy)
         
     | 
| 
      
 6 
     | 
    
         
            +
                      plan_self(:smart_proxy_id => smart_proxy.id)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def run
         
     | 
| 
      
 10 
     | 
    
         
            +
                      output[:pulp_tasks] = ::Katello::Pulp3::Api::Core.new(smart_proxy).purge_completed_tasks
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -454,6 +454,8 @@ module Katello 
     | 
|
| 
       454 
454 
     | 
    
         
             
                      ).each do |facet|
         
     | 
| 
       455 
455 
     | 
    
         
             
                        facet.update_applicability_counts
         
     | 
| 
       456 
456 
     | 
    
         
             
                        facet.update_errata_status
         
     | 
| 
      
 457 
     | 
    
         
            +
                      rescue NoMethodError
         
     | 
| 
      
 458 
     | 
    
         
            +
                        Rails.logger.warn _('Errata statuses not updated for deleted content facet with UUID %s') % facet.uuid
         
     | 
| 
       457 
459 
     | 
    
         
             
                      end
         
     | 
| 
       458 
460 
     | 
    
         
             
                    end
         
     | 
| 
       459 
461 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -133,7 +133,14 @@ module Katello 
     | 
|
| 
       133 
133 
     | 
    
         | 
| 
       134 
134 
     | 
    
         
             
                    repo_param[:mirroring_policy] = Katello::RootRepository::MIRRORING_POLICY_ADDITIVE if repo_param[:mirroring_policy].blank?
         
     | 
| 
       135 
135 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
                     
     | 
| 
      
 136 
     | 
    
         
            +
                    repo_param = repo_param.merge(:product_id => self.id)
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                    # Container push may concurrently call root add several times before the db can update.
         
     | 
| 
      
 139 
     | 
    
         
            +
                    if repo_param[:is_container_push]
         
     | 
| 
      
 140 
     | 
    
         
            +
                      RootRepository.create_or_find_by!(repo_param)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    else
         
     | 
| 
      
 142 
     | 
    
         
            +
                      RootRepository.new(repo_param)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
       137 
144 
     | 
    
         
             
                  end
         
     | 
| 
       138 
145 
     | 
    
         
             
                end
         
     | 
| 
       139 
146 
     | 
    
         
             
              end
         
     | 
| 
         @@ -118,7 +118,7 @@ module Katello 
     | 
|
| 
       118 
118 
     | 
    
         
             
                end}
         
     | 
| 
       119 
119 
     | 
    
         | 
| 
       120 
120 
     | 
    
         
             
                before_validation :set_pulp_id
         
     | 
| 
       121 
     | 
    
         
            -
                before_validation :set_container_repository_name, : 
     | 
| 
      
 121 
     | 
    
         
            +
                before_validation :set_container_repository_name, :unless => :skip_container_name?
         
     | 
| 
       122 
122 
     | 
    
         | 
| 
       123 
123 
     | 
    
         
             
                scope :has_url, -> { joins(:root).where.not("#{RootRepository.table_name}.url" => nil) }
         
     | 
| 
       124 
124 
     | 
    
         
             
                scope :on_demand, -> { joins(:root).where("#{RootRepository.table_name}.download_policy" => ::Katello::RootRepository::DOWNLOAD_ON_DEMAND) }
         
     | 
| 
         @@ -268,6 +268,10 @@ module Katello 
     | 
|
| 
       268 
268 
     | 
    
         
             
                  self.content_view_version.content_view
         
     | 
| 
       269 
269 
     | 
    
         
             
                end
         
     | 
| 
       270 
270 
     | 
    
         | 
| 
      
 271 
     | 
    
         
            +
                def skip_container_name?
         
     | 
| 
      
 272 
     | 
    
         
            +
                  self.library_instance? && self.root.docker? && self.root.is_container_push
         
     | 
| 
      
 273 
     | 
    
         
            +
                end
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
       271 
275 
     | 
    
         
             
                def library_instance?
         
     | 
| 
       272 
276 
     | 
    
         
             
                  self.content_view.default?
         
     | 
| 
       273 
277 
     | 
    
         
             
                end
         
     | 
| 
         @@ -17,7 +17,7 @@ module Katello 
     | 
|
| 
       17 
17 
     | 
    
         
             
                DOWNLOAD_POLICIES = [DOWNLOAD_IMMEDIATE, DOWNLOAD_ON_DEMAND].freeze
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                IGNORABLE_CONTENT_UNIT_TYPES = %w(srpm treeinfo).freeze
         
     | 
| 
       20 
     | 
    
         
            -
                CHECKSUM_TYPES = %w( 
     | 
| 
      
 20 
     | 
    
         
            +
                CHECKSUM_TYPES = %w(sha256 sha384 sha512).freeze
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                SUBSCRIBABLE_TYPES = [Repository::YUM_TYPE, Repository::OSTREE_TYPE, Repository::DEB_TYPE].freeze
         
     | 
| 
       23 
23 
     | 
    
         
             
                SKIPABLE_METADATA_TYPES = [Repository::YUM_TYPE, Repository::DEB_TYPE].freeze
         
     | 
| 
         @@ -112,6 +112,8 @@ module Katello 
     | 
|
| 
       112 
112 
     | 
    
         
             
                    only_integer: true
         
     | 
| 
       113 
113 
     | 
    
         
             
                  }
         
     | 
| 
       114 
114 
     | 
    
         | 
| 
      
 115 
     | 
    
         
            +
                validates :container_push_name_format, inclusion: { in: ['label', 'id'].freeze, allow_nil: true}
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
       115 
117 
     | 
    
         
             
                scope :subscribable, -> { where(content_type: RootRepository::SUBSCRIBABLE_TYPES) }
         
     | 
| 
       116 
118 
     | 
    
         
             
                scope :skipable_metadata_check, -> { where(content_type: RootRepository::SKIPABLE_METADATA_TYPES) }
         
     | 
| 
       117 
119 
     | 
    
         
             
                scope :has_url, -> { where.not(:url => nil) }
         
     | 
| 
         @@ -195,7 +197,7 @@ module Katello 
     | 
|
| 
       195 
197 
     | 
    
         
             
                def ensure_docker_repo_unprotected
         
     | 
| 
       196 
198 
     | 
    
         
             
                  unless unprotected
         
     | 
| 
       197 
199 
     | 
    
         
             
                    errors.add(:base, _("Container Image Repositories are not protected at this time. " \
         
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
      
 200 
     | 
    
         
            +
                                        "They need to be published via http to be available to containers."))
         
     | 
| 
       199 
201 
     | 
    
         
             
                  end
         
     | 
| 
       200 
202 
     | 
    
         
             
                end
         
     | 
| 
       201 
203 
     | 
    
         | 
| 
         @@ -36,7 +36,7 @@ module Katello 
     | 
|
| 
       36 
36 
     | 
    
         
             
                end
         
     | 
| 
       37 
37 
     | 
    
         | 
| 
       38 
38 
     | 
    
         
             
                def to_status(_options = {})
         
     | 
| 
       39 
     | 
    
         
            -
                  traces = host.host_traces.pluck(:app_type)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  traces = host.host_traces.reload.pluck(:app_type)
         
     | 
| 
       40 
40 
     | 
    
         
             
                  traces.delete(Katello::HostTracer::TRACE_APP_TYPE_SESSION)
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
                  if traces.include?(Katello::HostTracer::TRACE_APP_TYPE_STATIC)
         
     | 
| 
         @@ -176,6 +176,14 @@ module Katello 
     | 
|
| 
       176 
176 
     | 
    
         
             
                      nil
         
     | 
| 
       177 
177 
     | 
    
         
             
                    end
         
     | 
| 
       178 
178 
     | 
    
         | 
| 
      
 179 
     | 
    
         
            +
                    def purge_class
         
     | 
| 
      
 180 
     | 
    
         
            +
                      PulpcoreClient::Purge
         
     | 
| 
      
 181 
     | 
    
         
            +
                    end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                    def purge_completed_tasks
         
     | 
| 
      
 184 
     | 
    
         
            +
                      tasks_api.purge(purge_class.new(finished_before: DateTime.now - Setting[:completed_pulp_task_protection_days]))
         
     | 
| 
      
 185 
     | 
    
         
            +
                    end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
       179 
187 
     | 
    
         
             
                    def delete_orphans
         
     | 
| 
       180 
188 
     | 
    
         
             
                      [orphans_api.cleanup(PulpcoreClient::OrphansCleanup.new(orphan_protection_time: (smart_proxy.pulp_mirror? ? 0 : Setting[:orphan_protection_time])))]
         
     | 
| 
       181 
189 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -22,12 +22,7 @@ module Katello 
     | 
|
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
                    def publication_options(repository_version)
         
     | 
| 
       24 
24 
     | 
    
         
             
                      options = super(repository_version)
         
     | 
| 
       25 
     | 
    
         
            -
                      options.merge(
         
     | 
| 
       26 
     | 
    
         
            -
                        {
         
     | 
| 
       27 
     | 
    
         
            -
                          metadata_checksum_type: root.checksum_type,
         
     | 
| 
       28 
     | 
    
         
            -
                          package_checksum_type: root.checksum_type
         
     | 
| 
       29 
     | 
    
         
            -
                        }
         
     | 
| 
       30 
     | 
    
         
            -
                      )
         
     | 
| 
      
 25 
     | 
    
         
            +
                      options.merge(checksum_type: root.checksum_type)
         
     | 
| 
       31 
26 
     | 
    
         
             
                    end
         
     | 
| 
       32 
27 
     | 
    
         | 
| 
       33 
28 
     | 
    
         
             
                    def specific_create_options
         
     | 
| 
         @@ -1,3 +1,5 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            <% content_for(:javascripts) do %>
         
     | 
| 
      
 2 
     | 
    
         
            +
                <%= webpacked_plugins_js_for :katello %>
         
     | 
| 
      
 3 
     | 
    
         
            +
            <% end %>
         
     | 
| 
       2 
4 
     | 
    
         
             
            <% @smartProxyId= @smart_proxy.id %>
         
     | 
| 
       3 
5 
     | 
    
         
             
            <%= react_component('Content', smartProxyId: @smartProxyId, organizationId: Organization.current&.id,)  %>
         
     | 
| 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class AddContainerPushPropsToRepo < ActiveRecord::Migration[6.1]
         
     | 
| 
      
 2 
     | 
    
         
            +
              def change
         
     | 
| 
      
 3 
     | 
    
         
            +
                add_column :katello_root_repositories, :is_container_push, :boolean, default: false
         
     | 
| 
      
 4 
     | 
    
         
            +
                add_column :katello_root_repositories, :container_push_name, :string, default: nil
         
     | 
| 
      
 5 
     | 
    
         
            +
                add_column :katello_root_repositories, :container_push_name_format, :string, default: nil
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class RemoveSha1RepositoryChecksumType < ActiveRecord::Migration[6.1]
         
     | 
| 
      
 2 
     | 
    
         
            +
              def up
         
     | 
| 
      
 3 
     | 
    
         
            +
                ::Katello::Repository.where(saved_checksum_type: 'sha1').update(saved_checksum_type: nil)
         
     | 
| 
      
 4 
     | 
    
         
            +
                ::Katello::RootRepository.where(checksum_type: 'sha1').update(checksum_type: nil)
         
     | 
| 
      
 5 
     | 
    
         
            +
              end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def down
         
     | 
| 
      
 8 
     | 
    
         
            +
                fail ActiveRecord::IrreversibleMigration
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -10,7 +10,12 @@ 
     | 
|
| 
       10 
10 
     | 
    
         
             
            angular.module('Bastion.repositories').service('Checksum',
         
     | 
| 
       11 
11 
     | 
    
         
             
                ['translate', function (translate) {
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                    this.checksums = [ 
     | 
| 
      
 13 
     | 
    
         
            +
                    this.checksums = [
         
     | 
| 
      
 14 
     | 
    
         
            +
                        {name: translate('Default'), id: null},
         
     | 
| 
      
 15 
     | 
    
         
            +
                        {id: 'sha256', name: 'sha256'},
         
     | 
| 
      
 16 
     | 
    
         
            +
                        {id: 'sha384', name: 'sha384'},
         
     | 
| 
      
 17 
     | 
    
         
            +
                        {id: 'sha512', name: 'sha512'}
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ];
         
     | 
| 
       14 
19 
     | 
    
         | 
| 
       15 
20 
     | 
    
         
             
                    this.checksumType = function (checksum) {
         
     | 
| 
       16 
21 
     | 
    
         
             
                        if (checksum === null) {
         
     | 
| 
         @@ -371,9 +371,6 @@ 
     | 
|
| 
       371 
371 
     | 
    
         
             
                            ng-model="repository.checksum_type"
         
     | 
| 
       372 
372 
     | 
    
         
             
                            ng-options="type.id as type.name for type in checksums">
         
     | 
| 
       373 
373 
     | 
    
         
             
                    </select>
         
     | 
| 
       374 
     | 
    
         
            -
                    <p class="help-block" translate>
         
     | 
| 
       375 
     | 
    
         
            -
                      For older operating systems such as Red Hat Enterprise Linux 5 or CentOS 5 it is recommended to use sha1.
         
     | 
| 
       376 
     | 
    
         
            -
                    </p>
         
     | 
| 
       377 
374 
     | 
    
         
             
                  </div>
         
     | 
| 
       378 
375 
     | 
    
         | 
| 
       379 
376 
     | 
    
         
             
                  <div class="checkbox" ng-hide="repository.content_type === 'docker' || repository.content_type === 'ansible_collection'">
         
     | 
    
        data/lib/katello/plugin.rb
    CHANGED
    
    | 
         @@ -634,6 +634,12 @@ Foreman::Plugin.register :katello do 
     | 
|
| 
       634 
634 
     | 
    
         
             
                    full_name: N_('Orphaned Content Protection Time'),
         
     | 
| 
       635 
635 
     | 
    
         
             
                    description: N_('Time in minutes before content that is not contained within a repository and has not been accessed is considered orphaned.')
         
     | 
| 
       636 
636 
     | 
    
         | 
| 
      
 637 
     | 
    
         
            +
                  setting 'completed_pulp_task_protection_days',
         
     | 
| 
      
 638 
     | 
    
         
            +
                    type: :integer,
         
     | 
| 
      
 639 
     | 
    
         
            +
                    default: 30,
         
     | 
| 
      
 640 
     | 
    
         
            +
                    full_name: N_('Completed pulp task protection days'),
         
     | 
| 
      
 641 
     | 
    
         
            +
                    description: N_('How many days before a completed Pulp task is purged by Orphan Cleanup.')
         
     | 
| 
      
 642 
     | 
    
         
            +
             
     | 
| 
       637 
643 
     | 
    
         
             
                  setting 'remote_execution_prefer_registered_through_proxy',
         
     | 
| 
       638 
644 
     | 
    
         
             
                    type: :boolean,
         
     | 
| 
       639 
645 
     | 
    
         
             
                    default: false,
         
     | 
| 
         @@ -651,6 +657,12 @@ Foreman::Plugin.register :katello do 
     | 
|
| 
       651 
657 
     | 
    
         
             
                    default: true,
         
     | 
| 
       652 
658 
     | 
    
         
             
                    full_name: N_('Distribute archived content view versions'),
         
     | 
| 
       653 
659 
     | 
    
         
             
                    description: N_("If this is enabled, repositories of content view versions without environments (\"archived\") will be distributed at '/pulp/content/<organization>/content_views/<content view>/X.Y/...'.")
         
     | 
| 
      
 660 
     | 
    
         
            +
             
     | 
| 
      
 661 
     | 
    
         
            +
                  setting 'automatic_content_count_updates',
         
     | 
| 
      
 662 
     | 
    
         
            +
                    type: :boolean,
         
     | 
| 
      
 663 
     | 
    
         
            +
                    default: true,
         
     | 
| 
      
 664 
     | 
    
         
            +
                    full_name: N_('Calculate content counts on smart proxies automatically'),
         
     | 
| 
      
 665 
     | 
    
         
            +
                    description: N_("If this is enabled, content counts on smart proxies will be updated automatically after content sync.")
         
     | 
| 
       654 
666 
     | 
    
         
             
                end
         
     | 
| 
       655 
667 
     | 
    
         
             
              end
         
     | 
| 
       656 
668 
     | 
    
         | 
    
        data/lib/katello/version.rb
    CHANGED
    
    
    
        data/package.json
    CHANGED
    
    
| 
         @@ -1,59 +1,53 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import React, {  
     | 
| 
      
 1 
     | 
    
         
            +
            import React, { useState, useEffect } from 'react';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import PropTypes from 'prop-types';
         
     | 
| 
       3 
     | 
    
         
            -
            import  
     | 
| 
      
 3 
     | 
    
         
            +
            import { useUrlParams } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import { translate as __ } from 'foremanReact/common/I18n';
         
     | 
| 
       5 
5 
     | 
    
         
             
            import { orgId } from '../../services/api';
         
     | 
| 
       6 
6 
     | 
    
         
             
            import TableSchema from '../ModuleStreams/ModuleStreamsTableSchema';
         
     | 
| 
       7 
7 
     | 
    
         
             
            import GenericContentPage from '../../components/Content/GenericContentPage';
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
               
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
      
 9 
     | 
    
         
            +
            const ModuleStreamsPage = (props) => {
         
     | 
| 
      
 10 
     | 
    
         
            +
              const { searchParam } = useUrlParams();
         
     | 
| 
      
 11 
     | 
    
         
            +
              const [searchQuery, setSearchQuery] = useState(searchParam || '');
         
     | 
| 
      
 12 
     | 
    
         
            +
              const { getModuleStreams } = props;
         
     | 
| 
       12 
13 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
       16 
     | 
    
         
            -
                };
         
     | 
| 
       17 
     | 
    
         
            -
              }
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
              componentDidMount() {
         
     | 
| 
       20 
     | 
    
         
            -
                this.props.getModuleStreams({
         
     | 
| 
       21 
     | 
    
         
            -
                  search: this.state.searchQuery,
         
     | 
| 
      
 14 
     | 
    
         
            +
              useEffect(() => {
         
     | 
| 
      
 15 
     | 
    
         
            +
                getModuleStreams({
         
     | 
| 
      
 16 
     | 
    
         
            +
                  search: searchQuery,
         
     | 
| 
       22 
17 
     | 
    
         
             
                });
         
     | 
| 
       23 
     | 
    
         
            -
              }
         
     | 
| 
      
 18 
     | 
    
         
            +
              }, [getModuleStreams, searchQuery]);
         
     | 
| 
       24 
19 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
              onPaginationChange = (pagination) => {
         
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
      
 20 
     | 
    
         
            +
              const onPaginationChange = (pagination) => {
         
     | 
| 
      
 21 
     | 
    
         
            +
                props.getModuleStreams({
         
     | 
| 
       27 
22 
     | 
    
         
             
                  ...pagination,
         
     | 
| 
       28 
23 
     | 
    
         
             
                });
         
     | 
| 
       29 
24 
     | 
    
         
             
              };
         
     | 
| 
       30 
25 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
              onSearch = (search) => {
         
     | 
| 
       32 
     | 
    
         
            -
                 
     | 
| 
      
 26 
     | 
    
         
            +
              const onSearch = (search) => {
         
     | 
| 
      
 27 
     | 
    
         
            +
                props.getModuleStreams({ search });
         
     | 
| 
       33 
28 
     | 
    
         
             
              };
         
     | 
| 
       34 
29 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
              updateSearchQuery = ( 
     | 
| 
       36 
     | 
    
         
            -
                 
     | 
| 
      
 30 
     | 
    
         
            +
              const updateSearchQuery = (newSearchQuery) => {
         
     | 
| 
      
 31 
     | 
    
         
            +
                setSearchQuery(newSearchQuery);
         
     | 
| 
       37 
32 
     | 
    
         
             
              };
         
     | 
| 
       38 
33 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
               
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                 
     | 
| 
       42 
     | 
    
         
            -
                   
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
            }
         
     | 
| 
      
 34 
     | 
    
         
            +
              const { moduleStreams } = props;
         
     | 
| 
      
 35 
     | 
    
         
            +
              return (
         
     | 
| 
      
 36 
     | 
    
         
            +
                <GenericContentPage
         
     | 
| 
      
 37 
     | 
    
         
            +
                  header={__('Module Streams')}
         
     | 
| 
      
 38 
     | 
    
         
            +
                  content={moduleStreams}
         
     | 
| 
      
 39 
     | 
    
         
            +
                  tableSchema={TableSchema}
         
     | 
| 
      
 40 
     | 
    
         
            +
                  onSearch={onSearch}
         
     | 
| 
      
 41 
     | 
    
         
            +
                  autocompleteEndpoint="/katello/api/v2/module_streams"
         
     | 
| 
      
 42 
     | 
    
         
            +
                  autocompleteQueryParams={{ organization_id: orgId() }}
         
     | 
| 
      
 43 
     | 
    
         
            +
                  bookmarkController="katello_module_streams"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  updateSearchQuery={updateSearchQuery}
         
     | 
| 
      
 45 
     | 
    
         
            +
                  initialInputValue={searchQuery}
         
     | 
| 
      
 46 
     | 
    
         
            +
                  onPaginationChange={onPaginationChange}
         
     | 
| 
      
 47 
     | 
    
         
            +
                />
         
     | 
| 
      
 48 
     | 
    
         
            +
              );
         
     | 
| 
      
 49 
     | 
    
         
            +
            };
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       57 
51 
     | 
    
         | 
| 
       58 
52 
     | 
    
         
             
            ModuleStreamsPage.propTypes = {
         
     | 
| 
       59 
53 
     | 
    
         
             
              location: PropTypes.shape({
         
     | 
| 
         @@ -1,19 +1,21 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import React from 'react';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import { shallow } from 'enzyme';
         
     | 
| 
       3 
3 
     | 
    
         
             
            import toJson from 'enzyme-to-json';
         
     | 
| 
      
 4 
     | 
    
         
            +
            import * as hooks from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
         
     | 
| 
       4 
5 
     | 
    
         
             
            import ModuleStreamsPage from '../ModuleStreamsPage';
         
     | 
| 
       5 
6 
     | 
    
         
             
            import GenericContentPage from '../../../components/Content/GenericContentPage';
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            describe('Module streams page', () => {
         
     | 
| 
       8 
9 
     | 
    
         
             
              it('should render and contain appropiate components', async () => {
         
     | 
| 
       9 
10 
     | 
    
         
             
                const moduleStreams = {};
         
     | 
| 
       10 
     | 
    
         
            -
                 
     | 
| 
      
 11 
     | 
    
         
            +
                jest.spyOn(hooks, 'useUrlParams').mockImplementation(() => ({
         
     | 
| 
      
 12 
     | 
    
         
            +
                  searchParam: '',
         
     | 
| 
      
 13 
     | 
    
         
            +
                }));
         
     | 
| 
       11 
14 
     | 
    
         
             
                const getModuleStreams = () => {};
         
     | 
| 
       12 
15 
     | 
    
         | 
| 
       13 
16 
     | 
    
         
             
                const wrapper = shallow(<ModuleStreamsPage
         
     | 
| 
       14 
17 
     | 
    
         
             
                  moduleStreams={moduleStreams}
         
     | 
| 
       15 
18 
     | 
    
         
             
                  getModuleStreams={getModuleStreams}
         
     | 
| 
       16 
     | 
    
         
            -
                  location={mockLocation}
         
     | 
| 
       17 
19 
     | 
    
         
             
                />);
         
     | 
| 
       18 
20 
     | 
    
         | 
| 
       19 
21 
     | 
    
         
             
                expect(toJson(wrapper)).toMatchSnapshot();
         
     | 
| 
         @@ -137,6 +137,8 @@ class ManageManifestModal extends Component { 
     | 
|
| 
       137 
137 
     | 
    
         
             
                  return name;
         
     | 
| 
       138 
138 
     | 
    
         
             
                };
         
     | 
| 
       139 
139 
     | 
    
         | 
| 
      
 140 
     | 
    
         
            +
                const manifestExpiredMessage = manifestExpirationDate ? __('Your manifest expired on {expirationDate}. To continue using Red Hat content, import a new manifest.') : __('Your manifest has expired. To continue using Red Hat content, import a new manifest.');
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
       140 
142 
     | 
    
         
             
                return (
         
     | 
| 
       141 
143 
     | 
    
         
             
                  <ForemanModal id={MANAGE_MANIFEST_MODAL_ID} title={__('Manage Manifest')}>
         
     | 
| 
       142 
144 
     | 
    
         
             
                    <Tabs id="manifest-history-tabs">
         
     | 
| 
         @@ -181,7 +183,7 @@ class ManageManifestModal extends Component { 
     | 
|
| 
       181 
183 
     | 
    
         
             
                                      title={__('Manifest expired')}
         
     | 
| 
       182 
184 
     | 
    
         
             
                                    >
         
     | 
| 
       183 
185 
     | 
    
         
             
                                      <FormattedMessage
         
     | 
| 
       184 
     | 
    
         
            -
                                        defaultMessage={ 
     | 
| 
      
 186 
     | 
    
         
            +
                                        defaultMessage={manifestExpiredMessage}
         
     | 
| 
       185 
187 
     | 
    
         
             
                                        values={{
         
     | 
| 
       186 
188 
     | 
    
         
             
                                          expirationDate: new Date(manifestExpirationDate).toDateString(),
         
     | 
| 
       187 
189 
     | 
    
         
             
                                        }}
         
     | 
| 
         @@ -198,7 +200,7 @@ class ManageManifestModal extends Component { 
     | 
|
| 
       198 
200 
     | 
    
         
             
                                      {getManifestName()}
         
     | 
| 
       199 
201 
     | 
    
         
             
                                    </Col>
         
     | 
| 
       200 
202 
     | 
    
         
             
                                  </Row>
         
     | 
| 
       201 
     | 
    
         
            -
                                  {isManifestImported && manifestExpirationDate &&
         
     | 
| 
      
 203 
     | 
    
         
            +
                                  {isManifestImported && Boolean(manifestExpirationDate) &&
         
     | 
| 
       202 
204 
     | 
    
         
             
                                    <Row>
         
     | 
| 
       203 
205 
     | 
    
         
             
                                      <Col sm={5} />
         
     | 
| 
       204 
206 
     | 
    
         
             
                                      <Col sm={7}>
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: katello
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 4.13.0 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 4.13.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - N/A
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2024- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2024-06-27 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: rails
         
     | 
| 
         @@ -965,6 +965,7 @@ files: 
     | 
|
| 
       965 
965 
     | 
    
         
             
            - app/lib/actions/pulp3/orphan_cleanup/delete_orphan_remotes.rb
         
     | 
| 
       966 
966 
     | 
    
         
             
            - app/lib/actions/pulp3/orphan_cleanup/delete_orphan_repository_versions.rb
         
     | 
| 
       967 
967 
     | 
    
         
             
            - app/lib/actions/pulp3/orphan_cleanup/delete_orphaned_migrated_repositories.rb
         
     | 
| 
      
 968 
     | 
    
         
            +
            - app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb
         
     | 
| 
       968 
969 
     | 
    
         
             
            - app/lib/actions/pulp3/orphan_cleanup/remove_orphans.rb
         
     | 
| 
       969 
970 
     | 
    
         
             
            - app/lib/actions/pulp3/orphan_cleanup/remove_unneeded_repos.rb
         
     | 
| 
       970 
971 
     | 
    
         
             
            - app/lib/actions/pulp3/repository/commit_upload.rb
         
     | 
| 
         @@ -2058,6 +2059,8 @@ files: 
     | 
|
| 
       2058 
2059 
     | 
    
         
             
            - db/migrate/20240207191223_remove_entitlement_mode_host_statuses.rb
         
     | 
| 
       2059 
2060 
     | 
    
         
             
            - db/migrate/20240423112842_add_fields_to_katello_docker_manifest.rb
         
     | 
| 
       2060 
2061 
     | 
    
         
             
            - db/migrate/20240502192021_change_katello_repository_rpms_id_seq_to_big_int.rb
         
     | 
| 
      
 2062 
     | 
    
         
            +
            - db/migrate/20240520142245_add_container_push_props_to_repo.rb
         
     | 
| 
      
 2063 
     | 
    
         
            +
            - db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb
         
     | 
| 
       2061 
2064 
     | 
    
         
             
            - db/seeds.d/101-locations.rb
         
     | 
| 
       2062 
2065 
     | 
    
         
             
            - db/seeds.d/102-organizations.rb
         
     | 
| 
       2063 
2066 
     | 
    
         
             
            - db/seeds.d/104-proxy.rb
         
     | 
| 
         @@ -5392,13 +5395,10 @@ files: 
     | 
|
| 
       5392 
5395 
     | 
    
         
             
            - webpack/test-utils/nockWrapper.js
         
     | 
| 
       5393 
5396 
     | 
    
         
             
            - webpack/test-utils/react-testing-lib-wrapper.js
         
     | 
| 
       5394 
5397 
     | 
    
         
             
            - webpack/test_setup.js
         
     | 
| 
       5395 
     | 
    
         
            -
            - webpack/utils/__tests__/useParamsWithHash.test.js
         
     | 
| 
       5396 
5398 
     | 
    
         
             
            - webpack/utils/helpers.js
         
     | 
| 
       5397 
     | 
    
         
            -
            - webpack/utils/paramsFromHash.js
         
     | 
| 
       5398 
5399 
     | 
    
         
             
            - webpack/utils/useDebounce.js
         
     | 
| 
       5399 
5400 
     | 
    
         
             
            - webpack/utils/useEventListener.js
         
     | 
| 
       5400 
5401 
     | 
    
         
             
            - webpack/utils/useKatelloDocUrl.js
         
     | 
| 
       5401 
     | 
    
         
            -
            - webpack/utils/useUrlParams.js
         
     | 
| 
       5402 
5402 
     | 
    
         
             
            homepage: http://www.katello.org
         
     | 
| 
       5403 
5403 
     | 
    
         
             
            licenses:
         
     | 
| 
       5404 
5404 
     | 
    
         
             
            - GPL-2.0
         
     | 
| 
         @@ -5417,9 +5417,9 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       5417 
5417 
     | 
    
         
             
                  version: '4'
         
     | 
| 
       5418 
5418 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       5419 
5419 
     | 
    
         
             
              requirements:
         
     | 
| 
       5420 
     | 
    
         
            -
              - - " 
     | 
| 
      
 5420 
     | 
    
         
            +
              - - ">="
         
     | 
| 
       5421 
5421 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       5422 
     | 
    
         
            -
                  version:  
     | 
| 
      
 5422 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
       5423 
5423 
     | 
    
         
             
            requirements: []
         
     | 
| 
       5424 
5424 
     | 
    
         
             
            rubygems_version: 3.4.19
         
     | 
| 
       5425 
5425 
     | 
    
         
             
            signing_key:
         
     | 
| 
         @@ -1,22 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import paramsFromHash from '../paramsFromHash';
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            test('can parse both hash and query params', () => {
         
     | 
| 
       4 
     | 
    
         
            -
              const { hash, params: { foo } } = paramsFromHash('#filters?foo=bar');
         
     | 
| 
       5 
     | 
    
         
            -
              expect(hash).toBe('filters');
         
     | 
| 
       6 
     | 
    
         
            -
              expect(foo).toBe('bar');
         
     | 
| 
       7 
     | 
    
         
            -
            });
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
            test('can parse just hash', () => {
         
     | 
| 
       10 
     | 
    
         
            -
              const { hash } = paramsFromHash('#filters');
         
     | 
| 
       11 
     | 
    
         
            -
              expect(hash).toBe('filters');
         
     | 
| 
       12 
     | 
    
         
            -
            });
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
            test('can parse just query params', () => {
         
     | 
| 
       15 
     | 
    
         
            -
              const { params: { foo } } = paramsFromHash('?foo=bar');
         
     | 
| 
       16 
     | 
    
         
            -
              expect(foo).toBe('bar');
         
     | 
| 
       17 
     | 
    
         
            -
            });
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
            test("won't error with blank string", () => {
         
     | 
| 
       20 
     | 
    
         
            -
              const { hash } = paramsFromHash('');
         
     | 
| 
       21 
     | 
    
         
            -
              expect(hash).toBe('');
         
     | 
| 
       22 
     | 
    
         
            -
            });
         
     | 
| 
         @@ -1,16 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import qs from 'query-string';
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            /*
         
     | 
| 
       4 
     | 
    
         
            -
             For when you have a hash with your params in the URL
         
     | 
| 
       5 
     | 
    
         
            -
             pass in the the 'hash' object from react-router's useLocation();
         
     | 
| 
       6 
     | 
    
         
            -
             e.g. "mysite.com/foo#bar?baz=bop"
         
     | 
| 
       7 
     | 
    
         
            -
             will return { hash: 'bar', params: { baz: "bop" }}
         
     | 
| 
       8 
     | 
    
         
            -
            */
         
     | 
| 
       9 
     | 
    
         
            -
            const paramsFromHash = (hash) => {
         
     | 
| 
       10 
     | 
    
         
            -
              const [baseHash, queryParams = {}] = hash.split('?');
         
     | 
| 
       11 
     | 
    
         
            -
              const params = qs.parse(queryParams);
         
     | 
| 
       12 
     | 
    
         
            -
              const trimmedHash = baseHash.replace('#', '').replace('/', '');
         
     | 
| 
       13 
     | 
    
         
            -
              return { hash: trimmedHash, params };
         
     | 
| 
       14 
     | 
    
         
            -
            };
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
            export default paramsFromHash;
         
     | 
| 
         @@ -1,14 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import qs from 'query-string';
         
     | 
| 
       2 
     | 
    
         
            -
            import { useLocation } from 'react-router-dom';
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            // Allows hash and params e.g. "/foo#bar?query=baz"
         
     | 
| 
       5 
     | 
    
         
            -
            // returns { hash: "bar", params: { query: "baz" } }
         
     | 
| 
       6 
     | 
    
         
            -
            const useUrlParamsWithHash = () => {
         
     | 
| 
       7 
     | 
    
         
            -
              const { hash: fullParams } = useLocation();
         
     | 
| 
       8 
     | 
    
         
            -
              const [hash, queryParams = {}] = fullParams.split('?');
         
     | 
| 
       9 
     | 
    
         
            -
              const params = qs.parse(queryParams);
         
     | 
| 
       10 
     | 
    
         
            -
              const trimmedhash = hash.replace('#', '');
         
     | 
| 
       11 
     | 
    
         
            -
              return { hash: trimmedhash, params };
         
     | 
| 
       12 
     | 
    
         
            -
            };
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
            export default useUrlParamsWithHash;
         
     |