diff --git a/app/lib/actions/katello/content_view/incremental_updates.rb b/app/lib/actions/katello/content_view/incremental_updates.rb index ef5c814a035..50254733eb1 100644 --- a/app/lib/actions/katello/content_view/incremental_updates.rb +++ b/app/lib/actions/katello/content_view/incremental_updates.rb @@ -16,6 +16,8 @@ def version_environments_by_cv_id(version_environments) def plan(version_environments, composite_version_environments, content, dep_solve, hosts, description) old_new_version_map = {} output_for_version_ids = [] + # Extract composite CV IDs that will be updated via propagate to prevent duplicate auto-publish + propagated_composite_cv_ids = composite_version_environments.map { |cve| cve[:content_view_version].content_view_id }.compact.uniq sequence do concurrence do @@ -33,8 +35,9 @@ def plan(version_environments, composite_version_environments, content, dep_solv {:name => version.content_view.name, :version => version.version} end - action = plan_action(ContentViewVersion::IncrementalUpdate, version, - version_environment[:environments], :resolve_dependencies => dep_solve, :content => content, :description => description) + action = plan_action(ContentViewVersion::IncrementalUpdate, version, version_environment[:environments], + :resolve_dependencies => dep_solve, :content => content, :description => description, + :propagated_composite_cv_ids => propagated_composite_cv_ids) old_new_version_map[version] = action.new_content_view_version output_for_version_ids << {:version_id => action.new_content_view_version.id, :output => action.output} end diff --git a/app/lib/actions/katello/content_view_version/incremental_update.rb b/app/lib/actions/katello/content_view_version/incremental_update.rb index 5a0fdd83ca5..2ca030cd56c 100644 --- a/app/lib/actions/katello/content_view_version/incremental_update.rb +++ b/app/lib/actions/katello/content_view_version/incremental_update.rb @@ -26,6 +26,7 @@ def plan(old_version, environments, options = {}) description = options.fetch(:description, '') content = options.fetch(:content, {}) new_components = options.fetch(:new_components, []) + propagated_composite_cv_ids = options.fetch(:propagated_composite_cv_ids, []) is_composite = old_version.content_view.composite? all_components = is_composite ? calculate_components(old_version, new_components) : [] @@ -46,7 +47,8 @@ def plan(old_version, environments, options = {}) :new_content_view_version_id => publish_action.content_view_version_id, :environment_ids => environments.map(&:id), :user_id => ::User.current.id, :history_id => publish_action.history_id, - :old_version => old_version.id) + :old_version => old_version.id, + :propagated_composite_cv_ids => propagated_composite_cv_ids) if old_version.environments.present? plan_action(::Actions::Katello::ContentView::Promote, publish_action.version, @@ -120,7 +122,8 @@ def plan(old_version, environments, options = {}) :new_content_view_version_id => self.new_content_view_version.id, :environment_ids => environments.map(&:id), :user_id => ::User.current.id, :history_id => history.id, :copy_action_outputs => copy_action_outputs, - :old_version => old_version.id) + :old_version => old_version.id, + :propagated_composite_cv_ids => propagated_composite_cv_ids) promote(new_content_view_version, environments) end end @@ -223,8 +226,13 @@ def components_repo_instances(old_version_repo, new_component_versions) def run version = ::Katello::ContentViewVersion.find(input[:new_content_view_version_id]) if version.latest? && !version.content_view.composite? - output[:auto_publish_content_view_ids] = version.content_view.auto_publish_composites.pluck(:id) - output[:auto_publish_content_view_version_id] = version.id + # Exclude composites that are already being updated via propagate to prevent duplicate publishes + propagated_composite_cv_ids = input[:propagated_composite_cv_ids] || [] + auto_publish_ids = version.content_view.auto_publish_composites.pluck(:id) - propagated_composite_cv_ids + unless auto_publish_ids.empty? + output[:auto_publish_content_view_ids] = auto_publish_ids + output[:auto_publish_content_view_version_id] = version.id + end end content = { ::Katello::Erratum::CONTENT_TYPE => [], diff --git a/test/actions/katello/content_view_test.rb b/test/actions/katello/content_view_test.rb index f858ddb3a30..fd8b37607f1 100644 --- a/test/actions/katello/content_view_test.rb +++ b/test/actions/katello/content_view_test.rb @@ -951,7 +951,8 @@ class IncrementalUpdatesTest < TestBase plan_action(action, [{:content_view_version => content_view.version(library), :environments => [library]}], [], {:errata_ids => ["FOO"]}, true, [], "BadDescription") assert_action_planned_with(action, ::Actions::Katello::ContentViewVersion::IncrementalUpdate, content_view.version(library), [library], - :content => {:errata_ids => ["FOO"]}, :resolve_dependencies => true, :description => "BadDescription") + :resolve_dependencies => true, :content => {:errata_ids => ["FOO"]}, :description => "BadDescription", + :propagated_composite_cv_ids => []) end it 'plans with composite' do @@ -966,7 +967,8 @@ class IncrementalUpdatesTest < TestBase plan_action(action, [{:content_view_version => component, :environments => []}], [{:content_view_version => composite_version, :environments => [library]}], {:errata_ids => ["FOO"]}, true, [], "BadDescription") assert_action_planned_with(action, ::Actions::Katello::ContentViewVersion::IncrementalUpdate, component, [], - :content => {:errata_ids => ["FOO"]}, :resolve_dependencies => true, :description => "BadDescription") + :resolve_dependencies => true, :content => {:errata_ids => ["FOO"]}, :description => "BadDescription", + :propagated_composite_cv_ids => [composite_version.content_view_id]) assert_action_planned_with(action, ::Actions::Katello::ContentViewVersion::IncrementalUpdate, composite_version, [library], :new_components => [new_version], :description => "BadDescription") diff --git a/test/actions/katello/content_view_version/incremental_update_test.rb b/test/actions/katello/content_view_version/incremental_update_test.rb new file mode 100644 index 00000000000..d0088dd379b --- /dev/null +++ b/test/actions/katello/content_view_version/incremental_update_test.rb @@ -0,0 +1,174 @@ +require 'katello_test_helper' + +module Actions::Katello::ContentViewVersion + class IncrementalUpdateAutoPropagateTest < ActiveSupport::TestCase + include Dynflow::Testing + include Support::Actions::Fixtures + include FactoryBot::Syntax::Methods + + before do + User.current = users(:admin) + end + + let(:action_class) { ::Actions::Katello::ContentViewVersion::IncrementalUpdate } + let(:action) { create_action action_class } + + describe 'Auto Publish with Propagate' do + let(:component_cv) { katello_content_views(:library_dev_view) } + let(:component_version) { component_cv.versions.first } + let(:composite_cv) { katello_content_views(:composite_view) } + let(:mock_output) { {} } + + before do + # Set up composite to have auto_publish enabled + composite_cv.update!(auto_publish: true) + + # Ensure component has the composite in its relationships + unless component_version.composites.include?(composite_cv.versions.first) + ::Katello::ContentViewComponent.create!( + composite_content_view: composite_cv, + content_view: component_cv, + latest: true + ) + end + + # Stub the output to be writable + action.stubs(:output).returns(mock_output) + end + + it 'excludes propagated composites from auto-publish list' do + # Set input as if plan_self was called with propagated composite IDs + action.input.update( + new_content_view_version_id: component_version.id, + old_version: component_version.id, + propagated_composite_cv_ids: [composite_cv.id] + ) + + ::Katello::ContentViewVersion.stubs(:find).with(component_version.id).returns(component_version) + component_version.stubs(:repositories).returns([]) + component_version.stubs(:latest?).returns(true) + component_cv.stubs(:composite?).returns(false) + mock_relation = mock('auto_publish_composites') + mock_relation.expects(:pluck).with(:id).returns([composite_cv.id]) + component_cv.stubs(:auto_publish_composites).returns(mock_relation) + + action.run + assert_nil mock_output[:auto_publish_content_view_ids], "Expected auto_publish_content_view_ids to be nil when all composites are propagated" + assert_nil mock_output[:auto_publish_content_view_version_id] + end + + it 'includes composites in auto-publish list when not propagated' do + # Set input without propagated composites + action.input.update( + new_content_view_version_id: component_version.id, + old_version: component_version.id, + propagated_composite_cv_ids: [] + ) + + ::Katello::ContentViewVersion.stubs(:find).with(component_version.id).returns(component_version) + component_version.stubs(:repositories).returns([]) + component_version.stubs(:latest?).returns(true) + component_cv.stubs(:composite?).returns(false) + mock_relation = mock('auto_publish_composites') + mock_relation.expects(:pluck).with(:id).returns([composite_cv.id]) + component_cv.stubs(:auto_publish_composites).returns(mock_relation) + + action.run + auto_publish_ids = mock_output[:auto_publish_content_view_ids] + assert_equal [composite_cv.id], auto_publish_ids, "Expected composite to be in auto-publish list when not propagated" + assert_equal component_version.id, mock_output[:auto_publish_content_view_version_id] + end + + it 'handles partial propagation with multiple composites' do + composite_cv2 = create(:katello_content_view, :composite, + organization: composite_cv.organization, + auto_publish: true) + ::Katello::ContentViewComponent.create!( + composite_content_view: composite_cv2, + content_view: component_cv, + latest: true + ) + + action.input.update( + new_content_view_version_id: component_version.id, + old_version: component_version.id, + propagated_composite_cv_ids: [composite_cv.id] # Only first is propagated + ) + + ::Katello::ContentViewVersion.stubs(:find).with(component_version.id).returns(component_version) + component_version.stubs(:repositories).returns([]) + component_version.stubs(:latest?).returns(true) + component_cv.stubs(:composite?).returns(false) + mock_relation = mock('auto_publish_composites') + mock_relation.expects(:pluck).with(:id).returns([composite_cv.id, composite_cv2.id]) + component_cv.stubs(:auto_publish_composites).returns(mock_relation) + + action.run + auto_publish_ids = mock_output[:auto_publish_content_view_ids] + + refute_includes auto_publish_ids, composite_cv.id, "Propagated composite should be excluded" + assert_includes auto_publish_ids, composite_cv2.id, "Non-propagated composite should be included" + assert_equal [composite_cv2.id], auto_publish_ids, "Only non-propagated composite should be in list" + assert_equal component_version.id, mock_output[:auto_publish_content_view_version_id] + end + + it 'handles nil propagated_composite_cv_ids for backward compatibility' do + # Don't set propagated_composite_cv_ids at all (backward compatibility) + action.input.update( + new_content_view_version_id: component_version.id, + old_version: component_version.id + ) + + ::Katello::ContentViewVersion.stubs(:find).with(component_version.id).returns(component_version) + component_version.stubs(:repositories).returns([]) + component_version.stubs(:latest?).returns(true) + component_cv.stubs(:composite?).returns(false) + + mock_relation = mock('auto_publish_composites') + mock_relation.expects(:pluck).with(:id).returns([composite_cv.id]) + component_cv.stubs(:auto_publish_composites).returns(mock_relation) + + action.run + auto_publish_ids = mock_output[:auto_publish_content_view_ids] + assert_equal [composite_cv.id], auto_publish_ids, "Should include composite when propagated_composite_cv_ids is nil" + assert_equal component_version.id, mock_output[:auto_publish_content_view_version_id] + end + + it 'does not auto-publish for composite content views' do + composite_version = composite_cv.versions.first + + action.input.update( + new_content_view_version_id: composite_version.id, + old_version: composite_version.id, + propagated_composite_cv_ids: [] + ) + + ::Katello::ContentViewVersion.stubs(:find).with(composite_version.id).returns(composite_version) + composite_version.stubs(:latest?).returns(true) + composite_version.stubs(:repositories).returns([]) + composite_cv.stubs(:composite?).returns(true) + + action.run + assert_nil mock_output[:auto_publish_content_view_ids], "Composite CVs should not set auto_publish_content_view_ids" + assert_nil mock_output[:auto_publish_content_view_version_id] + end + + it 'does not auto-publish for non-latest versions' do + action.input.update( + new_content_view_version_id: component_version.id, + old_version: component_version.id, + propagated_composite_cv_ids: [] + ) + + ::Katello::ContentViewVersion.stubs(:find).with(component_version.id).returns(component_version) + component_version.stubs(:repositories).returns([]) + component_version.stubs(:latest?).returns(false) + component_cv.stubs(:composite?).returns(false) + + action.run + assert_nil mock_output[:auto_publish_content_view_ids], "Non-latest versions should not set auto_publish_content_view_ids" + assert_nil mock_output[:auto_publish_content_view_version_id] + end + end + end +end