From 72467400bca2aa658f422f4de79be927ad92c58c Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Tue, 9 Jul 2024 14:58:01 -0400 Subject: [PATCH] Fixes #37552 - container push repo content view support (#11028) * Fixes #37552 - container push CV support * Refs #37552 - authorize container push + disallow metadata regeneration * Refs #37552 - translate container registry errors * Refs #37552 - rename Environment::PublishRepositories to match container usage * Refs #37552 - add container push CV publish tests and limit PublishContainerRespositories to container repos * Refs #37552 - save container push distributions for all push endpoints --- .../registry/registry_proxies_controller.rb | 170 ++++++++++------ .../katello/api/v2/environments_controller.rb | 3 +- ...s.rb => publish_container_repositories.rb} | 18 +- app/lib/actions/katello/repository/create.rb | 6 +- .../katello/repository/metadata_generate.rb | 1 + .../repository/copy_all_units.rb | 4 + .../pulp3/orchestration/repository/delete.rb | 7 +- app/services/katello/pulp3/api/docker.rb | 10 + test/actions/katello/environment_test.rb | 20 ++ .../registry_proxies_controller_test.rb | 186 +++++++++++++----- .../api/v2/environments_controller_test.rb | 12 +- test/models/smart_proxy_sync_history_test.rb | 11 +- 12 files changed, 310 insertions(+), 138 deletions(-) rename app/lib/actions/katello/environment/{publish_repositories.rb => publish_container_repositories.rb} (52%) diff --git a/app/controllers/katello/api/registry/registry_proxies_controller.rb b/app/controllers/katello/api/registry/registry_proxies_controller.rb index 2d114eb169b..1ae512b8126 100644 --- a/app/controllers/katello/api/registry/registry_proxies_controller.rb +++ b/app/controllers/katello/api/registry/registry_proxies_controller.rb @@ -6,9 +6,7 @@ class Api::Registry::RegistryProxiesController < Api::V2::ApiController skip_before_action :authorize before_action :optional_authorize, only: [:token, :catalog] before_action :registry_authorize, except: [:token, :v1_search, :catalog] - before_action :authorize_repository_read, only: [:pull_manifest, :tags_list] - # TODO: authorize_repository_write commented out due to container push changes. Additional task needed to fix. - # before_action :authorize_repository_write, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest] + before_action :authorize_repository_read, only: [:pull_manifest, :tags_list, :check_blob, :pull_blob] before_action :container_push_prop_validation, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest] before_action :create_container_repo_if_needed, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, :push_manifest] skip_before_action :check_media_type, only: [:start_upload_blob, :upload_blob, :finish_upload_blob, @@ -143,7 +141,7 @@ def check_blob_push_field_syntax(props) unless props[:valid_format] return render_podman_error( "NAME_INVALID", - "Invalid format. Container pushes should follow 'organization_label/product_label/name' OR 'id/organization_id/product_id/name' schema.", + _("Invalid format. Container pushes should follow 'organization_label/product_label/name' OR 'id/organization_id/product_id/name' schema."), :bad_request ) end @@ -156,7 +154,7 @@ def check_blob_push_org_label(props) unless org_label.present? && org_label.length > 0 return render_podman_error( "NAME_INVALID", - "Invalid format. Organization label cannot be blank.", + _("Invalid format. Organization label cannot be blank."), :bad_request ) end @@ -173,11 +171,12 @@ def check_blob_push_org_label(props) unless root_repos.empty? return render_podman_error( "NAME_INVALID", - "Due to a change in your organizations, this container name has become "\ - "ambiguous (org name '#{org_label}'). If you wish to continue using this "\ - "container name, destroy the organization in conflict with '#{o.name} (id "\ - "#{o.id}). If you wish to keep both orgs, destroy '#{o.label}/#{prod.label}/"\ - "#{root_repos.first.label}' and retry your push using the id format.", + _("Due to a change in your organizations, this container name has become "\ + "ambiguous (org name '%{org_label}'). If you wish to continue using this "\ + "container name, destroy the organization in conflict with '%{o_name} (id "\ + "%{o_id}). If you wish to keep both orgs, destroy '%{o_label}/%{prod_label}/"\ + "%{root_repo_label}' and retry your push using the id format.") % + { org_label: org_label, o_name: o.name, o_id: o.id, o_label: o.label, prod_label: prod.label, root_repo_label: root_repos.first.label }, :conflict ) end @@ -188,14 +187,14 @@ def check_blob_push_org_label(props) # Otherwise tell them to try pushing with ID format return render_podman_error( "NAME_INVALID", - "Organization label '#{org_label}' is ambiguous. Try using an id-based container name.", + _("Organization label '%s' is ambiguous. Try using an id-based container name.") % org_label, :conflict ) end if org.length == 0 return render_podman_error( "NAME_UNKNOWN", - "Organization not found: '#{org_label}'", + _("Organization not found: '%s'") % org_label, :not_found ) end @@ -208,7 +207,7 @@ def check_blob_push_org_id(props) unless org_id.present? && org_id == org_id.to_i.to_s return render_podman_error( "NAME_INVALID", - "Invalid format. Organization id must be an integer without leading zeros.", + _("Invalid format. Organization id must be an integer without leading zeros."), :bad_request ) end @@ -216,7 +215,7 @@ def check_blob_push_org_id(props) if @organization.nil? return render_podman_error( "NAME_UNKNOWN", - "Organization id not found: '#{org_id}'", + _("Organization id not found: '%s'") % org_id, :not_found ) end @@ -228,7 +227,7 @@ def check_blob_push_product_label(props) unless prod_label.present? && prod_label.length > 0 return render_podman_error( "NAME_INVALID", - "Invalid format. Product label cannot be blank.", + _("Invalid format. Product label cannot be blank."), :bad_request ) end @@ -243,11 +242,12 @@ def check_blob_push_product_label(props) unless root_repos.empty? return render_podman_error( "NAME_INVALID", - "Due to a change in your products, this container name has become ambiguous "\ - "(product name '#{prod_label}'). If you wish to continue using this container "\ - "name, destroy the product in conflict with '#{prod.name}' (id #{prod.id}). If "\ - "you wish to keep both products, destroy '#{@organization.label}/#{prod.label}/"\ - "#{root_repos.first.label}' and retry your push using the id format.", + _("Due to a change in your products, this container name has become ambiguous "\ + "(product name '%{prod_label}'). If you wish to continue using this container "\ + "name, destroy the product in conflict with '%{prod_name}' (id %{prod_id}). If "\ + "you wish to keep both products, destroy '%{org_label}/%{prod_dot_label}/"\ + "%{root_repo_label}' and retry your push using the id format.") % + { prod_label: prod_label, prod_name: prod.name, prod_id: prod.id, org_label: @organization.label, prod_dot_label: prod.label, root_repo_label: root_repos.first.label }, :conflict ) end @@ -256,14 +256,14 @@ def check_blob_push_product_label(props) return render_podman_error( "NAME_INVALID", - "Product label '#{prod_label}' is ambiguous. Try using an id-based container name.", + _("Product label '%s' is ambiguous. Try using an id-based container name.") % prod_label, :conflict ) end if product.length == 0 return render_podman_error( "NAME_UNKNOWN", - "Product not found: '#{prod_label}'", + _("Product not found: '%s'") % prod_label, :not_found ) end @@ -276,7 +276,7 @@ def check_blob_push_product_id(props) unless prod_id.present? && prod_id == prod_id.to_i.to_s return render_podman_error( "NAME_INVALID", - "Invalid format. Product id must be an integer without leading zeros.", + _("Invalid format. Product id must be an integer without leading zeros."), :bad_request ) end @@ -284,7 +284,7 @@ def check_blob_push_product_id(props) if @product.nil? return render_podman_error( "NAME_UNKNOWN", - "Product id not found: '#{prod_id}'", + _("Product id not found: '%s'") % prod_id, :not_found ) end @@ -299,11 +299,16 @@ def get_root_repo_from_product(product, root_repo_name) return product.root_repositories.where(label: root_repo_name) end + def root_repository + @root_repository ||= get_root_repo_from_product(@product, @container_name)&.first + @root_repository + end + def check_blob_push_container(props) unless props[:name].present? && props[:name].length > 0 return render_podman_error( "NAME_INVALID", - "Invalid format. Container name cannot be blank.", + _("Invalid format. Container name cannot be blank."), :bad_request ) end @@ -321,7 +326,8 @@ def check_blob_push_container(props) if !root_repo.nil? && @container_push_name_format != root_repo.container_push_name_format return render_podman_error( "NAME_INVALID", - "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.", + _("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 or destroy and recreate the repository using your preferred schema.") % + {container_name: @container_name, root_repo_container_push_name: root_repo.container_push_name_format}, :conflict ) end @@ -330,6 +336,14 @@ def check_blob_push_container(props) end def create_container_repo_if_needed + unless @product.syncable? + return render_podman_error( + 'DENIED', + _("Requested access to '%s' is denied") % @container_name, + :not_found + ) + end + if get_root_repo_from_product(@product, @container_name).empty? root = @product.add_repo( name: @container_name, @@ -345,38 +359,73 @@ def create_container_repo_if_needed end end - def blob_push_cleanup - # after manifest upload, index content and set version href using pulp api - root_repo = get_root_repo_from_product(@product, @container_name)&.first - instance_repo = root_repo&.library_instance + def save_pulp_push_repository_href + instance_repo = root_repository&.library_instance - unless root_repo.present? && instance_repo.present? + unless root_repository.present? && instance_repo.present? return render_podman_error( "BLOB_UPLOAD_UNKNOWN", - "Could not locate local uploaded repository for content indexing.", + _("Could not locate local uploaded repository for content indexing."), :not_found ) end - api = ::Katello::Pulp3::Repository.api(SmartProxy.pulp_primary, ::Katello::Repository::DOCKER_TYPE).container_push_api - api_response = api.list(name: @container_path_input)&.results&.first - latest_version_href = api_response&.latest_version_href - pulp_href = api_response&.pulp_href + pulp_api = instance_repo.backend_service(SmartProxy.pulp_primary).api + push_repo_api_response = pulp_api.container_push_repo_for_name(@container_path_input) + + latest_version_href = push_repo_api_response&.latest_version_href + pulp_repo_href = push_repo_api_response&.pulp_href - if latest_version_href.empty? || pulp_href.empty? + if latest_version_href.empty? || pulp_repo_href.empty? return render_podman_error( "BLOB_UPLOAD_UNKNOWN", - "Could not locate repository properties for content indexing.", + _("Could not locate repository properties for content indexing."), :not_found ) end - instance_repo.update!(version_href: latest_version_href) - ::Katello::Pulp3::RepositoryReference.where(root_repository_id: instance_repo.root_id, - content_view_id: instance_repo.content_view.id, repository_href: pulp_href).create! - instance_repo.index_content + # The Pulp repository should not change after first creation + if root_repository.repository_references.empty? + ::Katello::Pulp3::RepositoryReference.where(root_repository_id: instance_repo.root_id, + content_view_id: instance_repo.content_view.id, + repository_href: pulp_repo_href).create! + end + return pulp_repo_href + end - true + def save_pulp_push_distribution_href(pulp_repo_href) + instance_repo = root_repository&.library_instance + pulp_api = instance_repo.backend_service(SmartProxy.pulp_primary).api + instance_repo = root_repository&.library_instance + distribution_api_response = pulp_api.container_push_distribution_for_repository(pulp_repo_href) + pulp_distribution_href = distribution_api_response&.pulp_href + + if pulp_distribution_href.empty? + return render_podman_error( + "BLOB_UPLOAD_UNKNOWN", + _("Could not locate Pulp distribution."), + :not_found + ) + end + dist = ::Katello::Pulp3::DistributionReference.where(path: @container_path_input, + href: pulp_distribution_href, + repository_id: instance_repo.id).first + if dist + if dist.href != pulp_distribution_href + dist.update(href: pulp_distribution_href) + end + else + ::Katello::Pulp3::DistributionReference.create!(path: @container_path_input, + href: pulp_distribution_href, + repository_id: instance_repo.id) + end + end + + def save_push_repo_hrefs + # After content upload, save Pulp hrefs. + pulp_repo_href = save_pulp_push_repository_href + return unless pulp_repo_href + save_pulp_push_distribution_href(pulp_repo_href) end def find_writable_repository @@ -499,6 +548,17 @@ def pull_blob redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers, max_redirects: 0) } end + def translated_headers_for_proxy + current_headers = {} + env = request.env.select do |key, _value| + key.match("^HTTP_.*") + end + env.each do |header| + current_headers[header[0].split('_')[1..-1].join('-')] = header[1] + end + current_headers + end + def start_upload_blob headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] @@ -509,20 +569,10 @@ def start_upload_blob response.header[key.to_s] = value end + save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end - def translated_headers_for_proxy - current_headers = {} - env = request.env.select do |key, _value| - key.match("^HTTP_.*") - end - env.each do |header| - current_headers[header[0].split('_')[1..-1].join('-')] = header[1] - end - current_headers - end - def upload_blob headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] @@ -535,6 +585,7 @@ def upload_blob response.header[key.to_s] = value end + save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end @@ -549,6 +600,7 @@ def finish_upload_blob response.header[key.to_s] = value end + save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end @@ -561,9 +613,9 @@ def push_manifest response.header[key.to_s] = value end - cleanup_result = blob_push_cleanup if pulp_response.code.between?(200, 299) - return false unless cleanup_result - + save_push_repo_hrefs if pulp_response.code.between?(200, 299) + # Indexing content is only needed after pushing manifests + root_repository.library_instance.index_content head pulp_response.code end @@ -724,7 +776,7 @@ def confirm_push_settings return true if SETTINGS.dig(:katello, :container_image_registry, :allow_push) render_podman_error( "UNSUPPORTED", - "Registry push is not enabled. To enable, add ':katello:'->':container_image_registry:'->':allow_push: true' in the katello settings file.", + _("Registry push is not enabled. To enable, add ':katello:'->':container_image_registry:'->':allow_push: true' in the katello settings file."), :unprocessable_entity ) end @@ -760,7 +812,7 @@ def render_podman_error(code, message, status = :bad_request) end def item_not_found(item) - render_podman_error("NAME_UNKNOWN", "#{item} was not found!", :not_found) + render_podman_error("NAME_UNKNOWN", _("%s was not found!") % item, :not_found) end end end diff --git a/app/controllers/katello/api/v2/environments_controller.rb b/app/controllers/katello/api/v2/environments_controller.rb index eaa403ec12e..72dc110de0e 100644 --- a/app/controllers/katello/api/v2/environments_controller.rb +++ b/app/controllers/katello/api/v2/environments_controller.rb @@ -108,8 +108,7 @@ def update update_params[:name] = params[:environment][:new_name] if params[:environment][:new_name] @environment.update!(update_params) if update_params[:registry_name_pattern] || update_params[:registry_unauthenticated_pull] - task = send(async ? :async_task : :sync_task, ::Actions::Katello::Environment::PublishRepositories, - @environment, content_type: Katello::Repository::DOCKER_TYPE) + task = send(async ? :async_task : :sync_task, ::Actions::Katello::Environment::PublishContainerRepositories, @environment) end if params.include?(:async) && async && task diff --git a/app/lib/actions/katello/environment/publish_repositories.rb b/app/lib/actions/katello/environment/publish_container_repositories.rb similarity index 52% rename from app/lib/actions/katello/environment/publish_repositories.rb rename to app/lib/actions/katello/environment/publish_container_repositories.rb index 338f660c41e..ee265d29f4a 100644 --- a/app/lib/actions/katello/environment/publish_repositories.rb +++ b/app/lib/actions/katello/environment/publish_container_repositories.rb @@ -1,7 +1,7 @@ module Actions module Katello module Environment - class PublishRepositories < Actions::EntryAction + class PublishContainerRepositories < Actions::EntryAction middleware.use ::Actions::Middleware::RemoteAction input_format do @@ -9,16 +9,18 @@ class PublishRepositories < Actions::EntryAction param :name end - def plan(env, options = {}) - repositories = options[:content_type] ? env.repositories.with_type(options[:content_type]) : env.repositories + def plan(env) + repositories = env.repositories.docker_type action_subject(env) concurrence do repositories.each do |repository| sequence do - repository.set_container_repository_name - repository.clear_smart_proxy_sync_histories - plan_action(::Actions::Katello::Repository::InstanceUpdate, repository) - plan_action(::Actions::Katello::Repository::CapsuleSync, repository) + unless repository.root.is_container_push && repository.library_instance? + repository.set_container_repository_name + repository.clear_smart_proxy_sync_histories + plan_action(::Actions::Katello::Repository::InstanceUpdate, repository) + plan_action(::Actions::Katello::Repository::CapsuleSync, repository) + end end end @@ -31,7 +33,7 @@ def rescue_strategy end def humanized_name - _("Publish Lifecycle Environment Repositories") + _("Publish Lifecycle Environment Container Repositories") end def humanized_input diff --git a/app/lib/actions/katello/repository/create.rb b/app/lib/actions/katello/repository/create.rb index 8716722335e..558908da846 100644 --- a/app/lib/actions/katello/repository/create.rb +++ b/app/lib/actions/katello/repository/create.rb @@ -2,7 +2,7 @@ module Actions module Katello module Repository class Create < Actions::EntryAction - # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity def plan(repository, args = {}) clone = args[:clone] || false force_repo_create = args[:force_repo_create] || false @@ -15,7 +15,7 @@ def plan(repository, args = {}) sequence do # Container push repositories will already be in pulp. The version_href is # directly updated after a push. - unless root.is_container_push + unless root.is_container_push && repository.in_default_view? create_action = plan_action(Pulp3::Orchestration::Repository::Create, repository, SmartProxy.pulp_primary, force_repo_create) return if create_action.error @@ -36,7 +36,7 @@ def plan(repository, args = {}) end # Container push repos do not need metadata generation or ACS (they do not sync) - unless root.is_container_push + unless root.is_container_push && repository.in_default_view? concurrence do plan_self(:repository_id => repository.id, :clone => clone) if !clone && repository.url.present? diff --git a/app/lib/actions/katello/repository/metadata_generate.rb b/app/lib/actions/katello/repository/metadata_generate.rb index 272dddf955c..efdb8a1eae5 100644 --- a/app/lib/actions/katello/repository/metadata_generate.rb +++ b/app/lib/actions/katello/repository/metadata_generate.rb @@ -3,6 +3,7 @@ module Katello module Repository class MetadataGenerate < Actions::EntryAction def plan(repository, options = {}) + return if repository.root.is_container_push && repository.library_instance? action_subject(repository) repository.check_ready_to_act! source_repository = options.fetch(:source_repository, nil) diff --git a/app/lib/actions/pulp3/orchestration/repository/copy_all_units.rb b/app/lib/actions/pulp3/orchestration/repository/copy_all_units.rb index e4d4f677b1a..405545f1d33 100644 --- a/app/lib/actions/pulp3/orchestration/repository/copy_all_units.rb +++ b/app/lib/actions/pulp3/orchestration/repository/copy_all_units.rb @@ -32,6 +32,10 @@ def plan(target_repo, smart_proxy, source_repositories, options = {}) plan_action(Actions::Pulp3::Repository::SaveVersion, target_repo, tasks: copy_actions.last.output[:pulp_tasks]) end end + elsif source_repositories.first.root.is_container_push + copy_action = plan_action(Actions::Pulp3::Repository::CopyContent, source_repositories.first, smart_proxy, target_repo, + copy_all: true) + plan_action(Actions::Pulp3::Repository::SaveVersion, target_repo, tasks: copy_action.output[:pulp_tasks]) else plan_self(source_version_repo_id: source_repositories.first.id, target_repo_id: target_repo.id) diff --git a/app/lib/actions/pulp3/orchestration/repository/delete.rb b/app/lib/actions/pulp3/orchestration/repository/delete.rb index 66e8534488b..c70acde38ac 100644 --- a/app/lib/actions/pulp3/orchestration/repository/delete.rb +++ b/app/lib/actions/pulp3/orchestration/repository/delete.rb @@ -9,10 +9,13 @@ def plan(repository, smart_proxy) plan_action(Actions::Pulp3::Repository::DeleteDistributions, repository.id, smart_proxy) if repository.content_view.default? - #we're deleting the library instance, so just delete the whole pulp3 repo + # Container push repositories must be deleted through the distribution + return if repository.root.is_container_push + + # We're deleting the library instance, so just delete the whole pulp3 repo plan_action(Actions::Pulp3::Repository::Delete, repository.id, smart_proxy) elsif repository.environment.nil? - #we're deleting the archived instance, so delete the version + # We're deleting the archived instance, so delete the version plan_action(Actions::Pulp3::Repository::DeleteVersion, repository, smart_proxy) end end diff --git a/app/services/katello/pulp3/api/docker.rb b/app/services/katello/pulp3/api/docker.rb index 3f6208009a1..8306fcf4c7c 100644 --- a/app/services/katello/pulp3/api/docker.rb +++ b/app/services/katello/pulp3/api/docker.rb @@ -19,6 +19,16 @@ def recursive_add_api def container_push_api PulpContainerClient::RepositoriesContainerPushApi.new(api_client) end + + def container_push_repo_for_name(name) + # There should be only one repository in Pulp with the requested name + container_push_api.list(name: name)&.results&.first + end + + def container_push_distribution_for_repository(repository_href) + # There should be only one repository in Pulp with the requested repository_href + distributions_api.list(repository: repository_href)&.results&.first + end end end end diff --git a/test/actions/katello/environment_test.rb b/test/actions/katello/environment_test.rb index 9ba04a78a0a..cef497bb964 100644 --- a/test/actions/katello/environment_test.rb +++ b/test/actions/katello/environment_test.rb @@ -14,6 +14,26 @@ class TestBase < ActiveSupport::TestCase end end + class PublishContainerRepositoriesTest < TestBase + let(:action_class) { ::Actions::Katello::Environment::PublishContainerRepositories } + let(:action) { create_action action_class } + + let(:environment) { stub } + + it 'does not plan for container push library repos' do + container_push_repo = ::Katello::RootRepository.find_by(name: 'busybox').library_instance + container_push_repo.root.update(is_container_push: true) + environment.stubs(:repositories).returns(::Katello::Repository.where(id: container_push_repo.id)) + container_push_repo.expects(:set_container_repository_name).never + container_push_repo.expects(:clear_smart_proxy_sync_histories).never + action.stubs(:action_subject).with(environment) + + plan_action(action, environment) + refute_action_planned(action, ::Actions::Katello::Repository::InstanceUpdate) + refute_action_planned(action, ::Actions::Katello::Repository::CapsuleSync) + end + end + class DestroyTest < TestBase let(:action_class) { ::Actions::Katello::Environment::Destroy } let(:action) { create_action action_class } diff --git a/test/controllers/api/registry/registry_proxies_controller_test.rb b/test/controllers/api/registry/registry_proxies_controller_test.rb index ffb8f464b6f..098699b9535 100644 --- a/test/controllers/api/registry/registry_proxies_controller_test.rb +++ b/test/controllers/api/registry/registry_proxies_controller_test.rb @@ -541,6 +541,11 @@ def setup_permissions @controller.expects(:check_blob_push_product_label).returns(true) @controller.expects(:check_blob_push_container).returns(true) @controller.expects(:create_container_repo_if_needed) + @controller.expects(:save_push_repo_hrefs).returns(true) + mock_root_repo = mock('root_repo') + mock_instance_repo = mock('instance_repo') + mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + @controller.stubs(:root_repository).returns(mock_root_repo) # The content type should be octet-stream, but action controller # throws a non-existence error since Mime::Type.lookup('application/octet-stream').to_sym is nil. @@ -565,6 +570,11 @@ def setup_permissions @controller.expects(:check_blob_push_product_label).returns(true) @controller.expects(:check_blob_push_container).returns(true) @controller.expects(:create_container_repo_if_needed) + @controller.expects(:save_push_repo_hrefs).returns(true) + mock_root_repo = mock('root_repo') + mock_instance_repo = mock('instance_repo') + mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + @controller.stubs(:root_repository).returns(mock_root_repo) # The content type should be octet-stream, but action controller # throws a non-existence error since Mime::Type.lookup('application/octet-stream').to_sym is nil. @@ -592,6 +602,11 @@ def setup_permissions @controller.expects(:check_blob_push_product_label).returns(true) @controller.expects(:check_blob_push_container).returns(true) @controller.expects(:create_container_repo_if_needed) + @controller.expects(:save_push_repo_hrefs).returns(true) + mock_root_repo = mock('root_repo') + mock_instance_repo = mock('instance_repo') + mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + @controller.stubs(:root_repository).returns(mock_root_repo) # The content type should be octet-stream, but action controller # throws a non-existence error since Mime::Type.lookup('application/octet-stream').to_sym is nil. @@ -611,14 +626,19 @@ def setup_permissions assert_equal 'Mars', resp.headers['Location'] end - it 'pushes a manifest' do + it 'pushes a manifest and indexes content' do repo_name = 'test_org/test_product/test_name' tag = 'latest' @controller.expects(:check_blob_push_org_label).returns(true) @controller.expects(:check_blob_push_product_label).returns(true) @controller.expects(:check_blob_push_container).returns(true) @controller.expects(:create_container_repo_if_needed) - @controller.expects(:blob_push_cleanup).returns(true) + @controller.expects(:save_push_repo_hrefs).returns(true) + mock_root_repo = mock('root_repo') + mock_instance_repo = mock('instance_repo') + mock_instance_repo.expects(:index_content) + mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + @controller.stubs(:root_repository).returns(mock_root_repo) # The content type should be application/vnd.oci.image.manifest.v1+json, but action controller # throws a non-existence error since Mime::Type.lookup('application/octet-stream').to_sym is nil. @@ -949,7 +969,7 @@ def setup_permissions refute @controller.check_blob_push_container(props) end - it 'creates container repo when needed' do + it 'creates a container repo if authorized' do container_name = "foo" container_push_name = "default_org/test/foo" container_push_name_format = "label" @@ -957,6 +977,7 @@ def setup_permissions mock_root_repositories = mock('root_repositories') mock_root_repositories.stubs(:where).with(label: container_name).returns([]) mock_product = mock('Product') + mock_product.expects(:syncable?).returns(true) mock_product.stubs(:root_repositories).returns(mock_root_repositories) mock_product.expects(:add_repo).with( name: container_name, @@ -980,12 +1001,30 @@ def setup_permissions assert @controller.create_container_repo_if_needed end + it 'does not create container repo when unauthorized' do + container_name = "foo" + container_push_name = "default_org/test/foo" + container_push_name_format = "label" + mock_root_repositories = mock('root_repositories') + mock_root_repositories.stubs(:where).with(label: container_name).returns([]) + mock_product = mock('Product') + mock_product.expects(:syncable?).returns(false) + mock_product.stubs(:root_repositories).returns(mock_root_repositories) + @controller.instance_variable_set(:@product, mock_product) + @controller.instance_variable_set(:@container_name, container_name) + @controller.instance_variable_set(:@container_path_input, container_push_name) + @controller.instance_variable_set(:@container_push_name_format, container_push_name_format) + expect_render_podman_error("DENIED", :not_found) + refute @controller.create_container_repo_if_needed + end + it 'does not create container repo if it already exists' do container_name = "foo" mock_root_repo = mock('root_repository') mock_root_repositories = mock('root_repositories') mock_root_repositories.stubs(:where).with(label: container_name).returns([mock_root_repo]) mock_product = mock('Product') + mock_product.expects(:syncable?).returns(true) mock_product.stubs(:root_repositories).returns(mock_root_repositories) mock_product.expects(:add_repo).never @controller.instance_variable_set(:@product, mock_product) @@ -994,12 +1033,14 @@ def setup_permissions @controller.create_container_repo_if_needed end - it 'updates hrefs and triggers content indexing' do + it 'updates hrefs' do container_name = "foo" container_push_name = "default_org/test/foo" latest_version_href = "asdfghjk" - pulp_href = "qwertyui" + pulp_repo_href = "repo_href" + pulp_distribution_href = "distribution_href" root_id = 8_675_309 + instance_id = ::Katello::RootRepository.find_by(name: 'busybox').library_instance.id content_view_id = 2 # mock the product, root repo, instance repo, content view @@ -1009,9 +1050,10 @@ def setup_permissions mock_instance_repo.expects(:update!).with(version_href: latest_version_href) mock_instance_repo.stubs(:root_id).returns(root_id) mock_instance_repo.stubs(:content_view).returns(mock_content_view) - mock_instance_repo.expects(:index_content) + mock_instance_repo.stubs(:id).returns(instance_id) mock_root_repo = mock('root_repository') mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + mock_root_repo.stubs(:repository_references).returns([]) mock_root_repositories = mock('root_repositories') mock_root_repositories.stubs(:where).with(label: container_name).returns([mock_root_repo]) mock_product = mock('Product') @@ -1019,15 +1061,15 @@ def setup_permissions mock_product.expects(:add_repo).never # mock the pulp api endpoint - mock_api_response_results = mock('mock_api_response_results') - mock_api_response_results.stubs(:latest_version_href).returns(latest_version_href) - mock_api_response_results.stubs(:pulp_href).returns(pulp_href) - mock_api_response = mock('mock_api_response') - mock_api_response.stubs(:results).returns([mock_api_response_results]) - mock_container_push_api = mock('container_push_api') - mock_container_push_api.expects(:list).with(name: container_push_name).returns(mock_api_response) - mock_repo_api = mock('repo_api') - mock_repo_api.stubs(:container_push_api).returns(mock_container_push_api) + mock_push_repo_api_response_results = mock('mock_push_repo_api_response_results') + mock_push_repo_api_response_results.stubs(:latest_version_href).returns(latest_version_href) + mock_push_repo_api_response_results.stubs(:pulp_href).returns(pulp_repo_href) + mock_distribution_api_response_results = mock('mock_distribution_api_response_results') + mock_distribution_api_response_results.stubs(:pulp_href).returns(pulp_distribution_href) + + mock_pulp_api = mock('pulp_api') + mock_pulp_api.expects(:container_push_repo_for_name).with(container_push_name).returns(mock_push_repo_api_response_results) + mock_pulp_api.expects(:container_push_distribution_for_repository).with(pulp_repo_href).returns(mock_distribution_api_response_results) # mock the repository reference mock_repo_reference = mock('repo_reference') @@ -1035,12 +1077,15 @@ def setup_permissions # set up pulp stubs mock_pulp_primary = mock('pulp_primary') + mock_backend_service = mock('backend_service') SmartProxy.stubs(:pulp_primary).returns(mock_pulp_primary) - ::Katello::Pulp3::Repository.expects(:api).with(mock_pulp_primary, ::Katello::Repository::DOCKER_TYPE).returns(mock_repo_api) + #::Katello::Pulp3::Repository.expects(:api).with(mock_pulp_primary, ::Katello::Repository::DOCKER_TYPE).returns(mock_repo_api) + mock_instance_repo.stubs(:backend_service).with(mock_pulp_primary).returns(mock_backend_service) + mock_backend_service.stubs(:api).returns(mock_pulp_api) ::Katello::Pulp3::RepositoryReference.stubs(:where).with( root_repository_id: root_id, content_view_id: content_view_id, - repository_href: pulp_href + repository_href: pulp_repo_href ).returns(mock_repo_reference) # set up the controller @@ -1048,12 +1093,15 @@ def setup_permissions @controller.instance_variable_set(:@container_name, container_name) @controller.instance_variable_set(:@container_path_input, container_push_name) - assert @controller.blob_push_cleanup + assert @controller.save_push_repo_hrefs end it 'rejects missing root repo on content indexing' do container_name = "foo" + mock_pulp_api = mock('pulp_api') + mock_instance_repo = mock('instance repo') + mock_instance_repo.stubs(:backend_service).returns(mock_pulp_api) mock_root_repositories = mock('root_repositories') mock_root_repositories.stubs(:where).with(label: container_name).returns([]) mock_product = mock('Product') @@ -1062,7 +1110,7 @@ def setup_permissions @controller.instance_variable_set(:@product, mock_product) @controller.instance_variable_set(:@container_name, container_name) expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) - refute @controller.blob_push_cleanup + refute @controller.save_push_repo_hrefs end it 'rejects missing instance repo on content indexing' do @@ -1078,41 +1126,80 @@ def setup_permissions @controller.instance_variable_set(:@product, mock_product) @controller.instance_variable_set(:@container_name, container_name) expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) - refute @controller.blob_push_cleanup + refute @controller.save_push_repo_hrefs + end + + it 'rejects missing repo api response on content indexing' do + container_name = "foo" + container_push_name = "default_org/test/foo" + + mock_instance_repo = mock('library_instance') + mock_root_repo = mock('root_repository') + mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) + mock_root_repositories = mock('root_repositories') + mock_root_repositories.stubs(:where).with(label: container_name).returns([mock_root_repo]) + mock_product = mock('Product') + mock_product.stubs(:root_repositories).returns(mock_root_repositories) + + mock_pulp_api = mock('pulp_api') + mock_pulp_api.expects(:container_push_repo_for_name).with(container_push_name).returns(nil) + + mock_pulp_primary = mock('pulp_primary') + SmartProxy.stubs(:pulp_primary).returns(mock_pulp_primary) + mock_backend_service = mock('backend_service') + mock_instance_repo.stubs(:backend_service).with(mock_pulp_primary).returns(mock_backend_service) + mock_backend_service.stubs(:api).returns(mock_pulp_api) + + @controller.instance_variable_set(:@product, mock_product) + @controller.instance_variable_set(:@container_name, container_name) + @controller.instance_variable_set(:@container_path_input, container_push_name) + expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) + refute @controller.save_push_repo_hrefs end - it 'rejects missing api response on content indexing' do + it 'rejects missing pulp_distribution_href on content indexing' do container_name = "foo" container_push_name = "default_org/test/foo" + pulp_repo_href = "repo_href" + latest_version_href = "latest_version_href" mock_instance_repo = mock('library_instance') mock_root_repo = mock('root_repository') + mock_root_repo.stubs(:repository_references).returns(['salmon']) mock_root_repo.stubs(:library_instance).returns(mock_instance_repo) mock_root_repositories = mock('root_repositories') mock_root_repositories.stubs(:where).with(label: container_name).returns([mock_root_repo]) mock_product = mock('Product') mock_product.stubs(:root_repositories).returns(mock_root_repositories) - mock_container_push_api = mock('container_push_api') - mock_container_push_api.expects(:list).with(name: container_push_name).returns(nil) - mock_repo_api = mock('repo_api') - mock_repo_api.stubs(:container_push_api).returns(mock_container_push_api) + mock_push_repo_api_response_results = mock('mock_push_repo_api_response_results') + mock_push_repo_api_response_results.stubs(:latest_version_href).returns(latest_version_href) + mock_push_repo_api_response_results.stubs(:pulp_href).returns(pulp_repo_href) + mock_distribution_api_response_results = mock('mock_distribution_api_response_results') + mock_distribution_api_response_results.stubs(:pulp_href).returns(nil) + + mock_pulp_api = mock('pulp_api') + mock_pulp_api.expects(:container_push_repo_for_name).with(container_push_name).returns(mock_push_repo_api_response_results) + mock_pulp_api.expects(:container_push_distribution_for_repository).with(pulp_repo_href).returns(mock_distribution_api_response_results) mock_pulp_primary = mock('pulp_primary') SmartProxy.stubs(:pulp_primary).returns(mock_pulp_primary) - ::Katello::Pulp3::Repository.expects(:api).with(mock_pulp_primary, ::Katello::Repository::DOCKER_TYPE).returns(mock_repo_api) + mock_backend_service = mock('backend_service') + mock_instance_repo.expects(:update!).with(version_href: latest_version_href) + mock_instance_repo.stubs(:backend_service).with(mock_pulp_primary).returns(mock_backend_service) + mock_backend_service.stubs(:api).returns(mock_pulp_api) @controller.instance_variable_set(:@product, mock_product) @controller.instance_variable_set(:@container_name, container_name) @controller.instance_variable_set(:@container_path_input, container_push_name) expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) - refute @controller.blob_push_cleanup + refute @controller.save_push_repo_hrefs end it 'rejects missing latest_version_href on content indexing' do container_name = "foo" container_push_name = "default_org/test/foo" - pulp_href = "qwertyui" + pulp_repo_href = "repo_href" mock_instance_repo = mock('library_instance') mock_root_repo = mock('root_repository') @@ -1122,25 +1209,24 @@ def setup_permissions mock_product = mock('Product') mock_product.stubs(:root_repositories).returns(mock_root_repositories) - mock_api_response_results = mock('mock_api_response_results') - mock_api_response_results.stubs(:latest_version_href).returns(nil) - mock_api_response_results.stubs(:pulp_href).returns(pulp_href) - mock_api_response = mock('mock_api_response') - mock_api_response.stubs(:results).returns([mock_api_response_results]) - mock_container_push_api = mock('container_push_api') - mock_container_push_api.expects(:list).with(name: container_push_name).returns(mock_api_response) - mock_repo_api = mock('repo_api') - mock_repo_api.stubs(:container_push_api).returns(mock_container_push_api) + mock_push_repo_api_response_results = mock('mock_push_repo_api_response_results') + mock_push_repo_api_response_results.stubs(:latest_version_href).returns(nil) + mock_push_repo_api_response_results.stubs(:pulp_href).returns(pulp_repo_href) + + mock_pulp_api = mock('pulp_api') + mock_pulp_api.expects(:container_push_repo_for_name).with(container_push_name).returns(mock_push_repo_api_response_results) mock_pulp_primary = mock('pulp_primary') SmartProxy.stubs(:pulp_primary).returns(mock_pulp_primary) - ::Katello::Pulp3::Repository.expects(:api).with(mock_pulp_primary, ::Katello::Repository::DOCKER_TYPE).returns(mock_repo_api) + mock_backend_service = mock('backend_service') + mock_instance_repo.stubs(:backend_service).with(mock_pulp_primary).returns(mock_backend_service) + mock_backend_service.stubs(:api).returns(mock_pulp_api) @controller.instance_variable_set(:@product, mock_product) @controller.instance_variable_set(:@container_name, container_name) @controller.instance_variable_set(:@container_path_input, container_push_name) expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) - refute @controller.blob_push_cleanup + refute @controller.save_push_repo_hrefs end it 'rejects missing pulp_href on content indexing' do @@ -1155,27 +1241,25 @@ def setup_permissions mock_root_repositories.stubs(:where).with(label: container_name).returns([mock_root_repo]) mock_product = mock('Product') mock_product.stubs(:root_repositories).returns(mock_root_repositories) - mock_product.expects(:add_repo).never - mock_api_response_results = mock('mock_api_response_results') - mock_api_response_results.stubs(:latest_version_href).returns(latest_version_href) - mock_api_response_results.stubs(:pulp_href).returns(nil) - mock_api_response = mock('mock_api_response') - mock_api_response.stubs(:results).returns([mock_api_response_results]) - mock_container_push_api = mock('container_push_api') - mock_container_push_api.expects(:list).with(name: container_push_name).returns(mock_api_response) - mock_repo_api = mock('repo_api') - mock_repo_api.stubs(:container_push_api).returns(mock_container_push_api) + mock_push_repo_api_response_results = mock('mock_push_repo_api_response_results') + mock_push_repo_api_response_results.stubs(:latest_version_href).returns(latest_version_href) + mock_push_repo_api_response_results.stubs(:pulp_href).returns(nil) + + mock_pulp_api = mock('pulp_api') + mock_pulp_api.expects(:container_push_repo_for_name).with(container_push_name).returns(mock_push_repo_api_response_results) mock_pulp_primary = mock('pulp_primary') SmartProxy.stubs(:pulp_primary).returns(mock_pulp_primary) - ::Katello::Pulp3::Repository.expects(:api).with(mock_pulp_primary, ::Katello::Repository::DOCKER_TYPE).returns(mock_repo_api) + mock_backend_service = mock('backend_service') + mock_instance_repo.stubs(:backend_service).with(mock_pulp_primary).returns(mock_backend_service) + mock_backend_service.stubs(:api).returns(mock_pulp_api) @controller.instance_variable_set(:@product, mock_product) @controller.instance_variable_set(:@container_name, container_name) @controller.instance_variable_set(:@container_path_input, container_push_name) expect_render_podman_error("BLOB_UPLOAD_UNKNOWN", :not_found) - refute @controller.blob_push_cleanup + refute @controller.save_push_repo_hrefs end def mock_pulp_response(code, headers) diff --git a/test/controllers/api/v2/environments_controller_test.rb b/test/controllers/api/v2/environments_controller_test.rb index 52cbcfb7fd6..42cd331a174 100644 --- a/test/controllers/api/v2/environments_controller_test.rb +++ b/test/controllers/api/v2/environments_controller_test.rb @@ -160,8 +160,7 @@ def test_update def test_update_pattern_async original_label = @staging.label - assert_async_task(::Actions::Katello::Environment::PublishRepositories, @staging, - content_type: Katello::Repository::DOCKER_TYPE) + assert_async_task(::Actions::Katello::Environment::PublishContainerRepositories, @staging) put :update, params: { :organization_id => @organization.id, :id => @staging.id, :environment => { @@ -180,8 +179,7 @@ def test_update_pattern_async def test_update_pattern_sync original_label = @staging.label - assert_sync_task(::Actions::Katello::Environment::PublishRepositories, @staging, - content_type: Katello::Repository::DOCKER_TYPE) + assert_sync_task(::Actions::Katello::Environment::PublishContainerRepositories, @staging) put :update, params: { :organization_id => @organization.id, :id => @staging.id, :async => false, @@ -201,8 +199,7 @@ def test_update_pattern_sync def test_update_pull_async original_label = @staging.label - assert_async_task(::Actions::Katello::Environment::PublishRepositories, @staging, - content_type: Katello::Repository::DOCKER_TYPE) + assert_async_task(::Actions::Katello::Environment::PublishContainerRepositories, @staging) put :update, params: { :organization_id => @organization.id, :id => @staging.id, :environment => { @@ -221,8 +218,7 @@ def test_update_pull_async def test_update_pull_sync original_label = @staging.label - assert_sync_task(::Actions::Katello::Environment::PublishRepositories, @staging, - content_type: Katello::Repository::DOCKER_TYPE) + assert_sync_task(::Actions::Katello::Environment::PublishContainerRepositories, @staging) put :update, params: { :organization_id => @organization.id, :id => @staging.id, :async => false, diff --git a/test/models/smart_proxy_sync_history_test.rb b/test/models/smart_proxy_sync_history_test.rb index 3d1dd34df63..520a11c5dfc 100644 --- a/test/models/smart_proxy_sync_history_test.rb +++ b/test/models/smart_proxy_sync_history_test.rb @@ -53,12 +53,13 @@ def test_clear_history_on_smart_proxy def test_clear_history_on_publish_repositories User.current = users(:admin) - @repo.create_smart_proxy_sync_history(proxy_with_pulp) + busybox = katello_repositories(:busybox) + busybox.create_smart_proxy_sync_history(proxy_with_pulp) library = katello_environments(:library) - library.expects(:repositories).returns([@repo]) - ::Actions::Katello::Environment::PublishRepositories.any_instance.expects(:plan_action).twice - ::ForemanTasks.sync_task(::Actions::Katello::Environment::PublishRepositories, library) - assert_equal @repo.smart_proxy_sync_histories.count, 0 + library.expects(:repositories).returns(::Katello::Repository.where(id: busybox.id)) + ::Actions::Katello::Environment::PublishContainerRepositories.any_instance.expects(:plan_action).twice + ::ForemanTasks.sync_task(::Actions::Katello::Environment::PublishContainerRepositories, library) + assert_equal busybox.smart_proxy_sync_histories.count, 0 end end end