From d73f3062c232fd874f6981a249c36e620fe0a905 Mon Sep 17 00:00:00 2001 From: Jakub Duchek Date: Fri, 21 Aug 2026 14:54:56 +0200 Subject: [PATCH] Fixes #39684 - Scope Puppet autocomplete by taxonomy Limit Puppet class and config group suggestions to assignments from hosts and host groups visible in the current organization and location. Cover both taxonomy dimensions and resource types. Assisted-By: Codex 5.6 Sol High --- app/models/foreman_puppet/config_group.rb | 4 +- .../foreman_puppet/host_config_group.rb | 12 ++++++ app/models/foreman_puppet/puppetclass.rb | 17 +++++++-- test/models/foreman_puppet/host_test.rb | 37 +++++++++++++++++++ test/models/foreman_puppet/hostgroup_test.rb | 35 ++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/app/models/foreman_puppet/config_group.rb b/app/models/foreman_puppet/config_group.rb index ef3d8949..5b6869ce 100644 --- a/app/models/foreman_puppet/config_group.rb +++ b/app/models/foreman_puppet/config_group.rb @@ -16,7 +16,9 @@ class ConfigGroup < ApplicationRecord validates :name, presence: true, uniqueness: true - scope :assigned_to_hosts, -> { where(id: ForemanPuppet::HostConfigGroup.select(:config_group_id)) } + scope :assigned_to_hosts, lambda { + where(id: ForemanPuppet::HostConfigGroup.assigned_to_taxonomy.select(:config_group_id)) + } scoped_search on: :name, complete_value: true scoped_search relation: :puppetclasses, on: :name, complete_value: true, rename: :puppetclass, only_explicit: true, operators: ['= ', '~ '] diff --git a/app/models/foreman_puppet/host_config_group.rb b/app/models/foreman_puppet/host_config_group.rb index 80102ddd..8b9f10e1 100644 --- a/app/models/foreman_puppet/host_config_group.rb +++ b/app/models/foreman_puppet/host_config_group.rb @@ -8,6 +8,18 @@ class HostConfigGroup < ApplicationRecord validates :host_id, uniqueness: { scope: %i[config_group_id host_type] } + scope :assigned_to_taxonomy, lambda { + host_facets = ForemanPuppet::HostPuppetFacet + .where(host_id: ::Host::Managed.reorder(nil).select(:id)) + .select(:id) + hostgroup_facets = ForemanPuppet::HostgroupPuppetFacet + .where(hostgroup_id: ::Hostgroup.unscoped.with_taxonomy_scope.reorder(nil).select(:id)) + .select(:id) + + where(host_type: ForemanPuppet::HostPuppetFacet.polymorphic_name, host_id: host_facets) + .or(where(host_type: ForemanPuppet::HostgroupPuppetFacet.polymorphic_name, host_id: hostgroup_facets)) + } + def check_permissions_after_save true end diff --git a/app/models/foreman_puppet/puppetclass.rb b/app/models/foreman_puppet/puppetclass.rb index ca8e3db3..403d994d 100644 --- a/app/models/foreman_puppet/puppetclass.rb +++ b/app/models/foreman_puppet/puppetclass.rb @@ -42,10 +42,21 @@ class Puppetclass < ApplicationRecord default_scope -> { order(:name) } scope :assigned_to_hosts, lambda { - direct = ForemanPuppet::HostClass.select(:puppetclass_id) - via_hostgroup = ForemanPuppet::HostgroupClass.select(:puppetclass_id) + host_facets = ForemanPuppet::HostPuppetFacet + .where(host_id: ::Host::Managed.reorder(nil).select(:id)) + .select(:id) + hostgroup_facets = ForemanPuppet::HostgroupPuppetFacet + .where(hostgroup_id: ::Hostgroup.unscoped.with_taxonomy_scope.reorder(nil).select(:id)) + .select(:id) + direct = ForemanPuppet::HostClass + .where(host_puppet_facet_id: host_facets) + .select(:puppetclass_id) + via_hostgroup = ForemanPuppet::HostgroupClass + .where(hostgroup_puppet_facet_id: hostgroup_facets) + .select(:puppetclass_id) + config_group_ids = ForemanPuppet::HostConfigGroup.assigned_to_taxonomy.select(:config_group_id) via_config_group = ForemanPuppet::ConfigGroupClass - .where(config_group_id: ForemanPuppet::HostConfigGroup.select(:config_group_id)) + .where(config_group_id: config_group_ids) .select(:puppetclass_id) where(id: direct).or(where(id: via_hostgroup)).or(where(id: via_config_group)) diff --git a/test/models/foreman_puppet/host_test.rb b/test/models/foreman_puppet/host_test.rb index 46319e19..0e9caf42 100644 --- a/test/models/foreman_puppet/host_test.rb +++ b/test/models/foreman_puppet/host_test.rb @@ -176,6 +176,43 @@ class HostTest < ActiveSupport::TestCase assert_not_includes completions, %(config_group = "#{config_group.name}") end + test 'limits Puppet assignment completions to the current organization' do + organization = taxonomies(:organization1) + location = taxonomies(:location1) + visible_host = FactoryBot.create( + :host, + :with_puppet_enc, + :with_puppetclass, + organization: organization, + location: location + ) + hidden_host = FactoryBot.create( + :host, + :with_puppet_enc, + :with_puppetclass, + organization: FactoryBot.create(:organization), + location: location + ) + visible_config_group = FactoryBot.create(:config_group) + hidden_config_group = FactoryBot.create(:config_group) + visible_host.puppet.config_groups << visible_config_group + hidden_host.puppet.config_groups << hidden_config_group + Organization.current = organization + Location.current = nil + + visible_puppetclass = visible_host.puppet.puppetclasses.first + hidden_puppetclass = hidden_host.puppet.puppetclasses.first + visible_puppetclasses = ::Host::Managed.complete_for("puppetclass = #{visible_puppetclass.name}") + hidden_puppetclasses = ::Host::Managed.complete_for("puppetclass = #{hidden_puppetclass.name}") + visible_config_groups = ::Host::Managed.complete_for("config_group = #{visible_config_group.name}") + hidden_config_groups = ::Host::Managed.complete_for("config_group = #{hidden_config_group.name}") + + assert_includes visible_puppetclasses, %(puppetclass = "#{visible_puppetclass.name}") + assert_not_includes hidden_puppetclasses, %(puppetclass = "#{hidden_puppetclass.name}") + assert_includes visible_config_groups, %(config_group = "#{visible_config_group.name}") + assert_not_includes hidden_config_groups, %(config_group = "#{hidden_config_group.name}") + end + test 'preserves core host value completions' do completions = as_admin { ::Host::Managed.complete_for('user.login =') } diff --git a/test/models/foreman_puppet/hostgroup_test.rb b/test/models/foreman_puppet/hostgroup_test.rb index 3c83cb73..7bcdfcc7 100644 --- a/test/models/foreman_puppet/hostgroup_test.rb +++ b/test/models/foreman_puppet/hostgroup_test.rb @@ -47,6 +47,41 @@ class HostgroupTest < ActiveSupport::TestCase assert_includes completions, %(puppetclass = "#{puppetclass.name}") end + test 'limits Puppet assignment completions to the current location' do + organization = taxonomies(:organization1) + location = taxonomies(:location1) + visible_hostgroup = FactoryBot.create( + :hostgroup, + :with_puppet_enc, + :with_puppetclass, + organizations: [organization], + locations: [location] + ) + hidden_hostgroup = FactoryBot.create( + :hostgroup, + :with_puppet_enc, + :with_puppetclass, + organizations: [organization], + locations: [FactoryBot.create(:location)] + ) + visible_config_group = visible_hostgroup.puppet.config_groups.first + hidden_config_group = hidden_hostgroup.puppet.config_groups.first + Organization.current = nil + Location.current = location + + visible_puppetclass = visible_hostgroup.puppet.puppetclasses.first + hidden_puppetclass = hidden_hostgroup.puppet.puppetclasses.first + visible_puppetclasses = ::Hostgroup.complete_for("puppetclass = #{visible_puppetclass.name}") + hidden_puppetclasses = ::Hostgroup.complete_for("puppetclass = #{hidden_puppetclass.name}") + visible_config_groups = ::Hostgroup.complete_for("config_group = #{visible_config_group.name}") + hidden_config_groups = ::Hostgroup.complete_for("config_group = #{hidden_config_group.name}") + + assert_includes visible_puppetclasses, %(puppetclass = "#{visible_puppetclass.name}") + assert_not_includes hidden_puppetclasses, %(puppetclass = "#{hidden_puppetclass.name}") + assert_includes visible_config_groups, %(config_group = "#{visible_config_group.name}") + assert_not_includes hidden_config_groups, %(config_group = "#{hidden_config_group.name}") + end + test 'searches Puppet class values assigned through a config group' do config_group = hostgroup.puppet.config_groups.first puppetclass = FactoryBot.create(:puppetclass)