foreman_puppet 10.1.3 → 11.1.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/foreman_puppet/extensions/hosts_controller_extensions.rb +1 -1
  3. data/app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb +75 -1
  4. data/app/models/concerns/foreman_puppet/extensions/host.rb +2 -2
  5. data/app/models/concerns/foreman_puppet/extensions/host_common.rb +35 -0
  6. data/app/models/concerns/foreman_puppet/extensions/hostgroup.rb +10 -3
  7. data/app/models/foreman_puppet/config_group.rb +6 -0
  8. data/app/models/foreman_puppet/puppetclass.rb +14 -0
  9. data/app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb +18 -0
  10. data/config/api_routes.rb +1 -0
  11. data/db/seeds.d/111_puppet_proxy_feature.rb +1 -1
  12. data/lib/foreman_puppet/register.rb +2 -1
  13. data/lib/foreman_puppet/version.rb +1 -1
  14. data/test/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller_test.rb +93 -0
  15. data/test/models/foreman_puppet/host_test.rb +66 -0
  16. data/test/models/foreman_puppet/hostgroup_test.rb +32 -0
  17. data/test/services/foreman_puppet/bulk_hosts_manager_test.rb +45 -0
  18. data/webpack/__mocks__/foremanReact/redux/API/index.js +8 -0
  19. data/webpack/global_index.js +18 -1
  20. data/webpack/src/Extends/Hosts/ActionsBar/index.js +20 -0
  21. data/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/__tests__/actions.test.js +0 -7
  22. data/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/index.js +9 -13
  23. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/__tests__/index.test.js +3 -0
  24. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/index.js +2 -0
  25. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/BulkChangePuppetEnvironmentModal.js +239 -0
  26. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/BulkChangePuppetEnvironmentModal.test.js +155 -0
  27. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/actions.test.js +49 -0
  28. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/__tests__/index.test.js +66 -0
  29. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/actions.js +37 -0
  30. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment/index.js +30 -0
  31. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/__tests__/index.test.js +160 -44
  32. data/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/index.js +2 -0
  33. data/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/__tests__/actions.test.js +0 -6
  34. data/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/index.js +8 -13
  35. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/__tests__/index.test.js +138 -38
  36. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/index.js +2 -0
  37. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/BulkRemovePuppetEnvironmentModal.js +148 -0
  38. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/BulkRemovePuppetEnvironmentModal.test.js +80 -0
  39. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/__tests__/index.test.js +66 -0
  40. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment/index.js +30 -0
  41. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/__tests__/index.test.js +125 -33
  42. data/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/index.js +2 -0
  43. data/webpack/src/Router/routes.fixtures.js +13 -0
  44. data/webpack/src/Router/routes.test.js +68 -8
  45. data/webpack/src/foreman_puppet_host_form.test.js +3 -1
  46. data/webpack/test_setup.js +1 -0
  47. metadata +14 -3
  48. data/webpack/src/Router/__snapshots__/routes.test.js.snap +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cee4c4bc88e7e50cc6215f4d63bce66361304d94687507338c89333f3bd81e39
4
- data.tar.gz: db7d58c859b13521f37097955a30da8f23d8437934f1a307907a6ab6c73c6f6c
3
+ metadata.gz: 5aa351556f85774a9796437e9ee6fd0671f46db4bb3519aa6a94c1691c90d6ca
4
+ data.tar.gz: a9507d1ae1d1961d4256eba509e88e72f5ee1124d2d2532751f6f3ff7b2e2ac2
5
5
  SHA512:
6
- metadata.gz: 5ffcc0c52303eb7b4e402351d56536449396888f98038756acf318ec38b0dd9616d003747d6edc987ebd08a7b68f8c46328052a468eea0e5cd8dc31c617dc1af
7
- data.tar.gz: '048e3527e272f20bba27026fd9d65cdfc00686cf8a4bdc6ad078f5e3fee6e874ee3eef042a9694369a282d5a7a201b324fb99d2d8b4c50069345dbd220fe620a'
6
+ metadata.gz: 31593435b997b9202741f97a709aa40bbd57ed82f6c09aeadbe8e83432d0273b0f2523aebe0a13cf03f3e534592418091f21da8eb768cad3dfc7f0a68c1c3277
7
+ data.tar.gz: 4625c5cfbefe7af792e45ffad193ca1f0867aac29ac3596c003072aec34c366d3ade137244a861802ddab8d75c45651343c76901d871bfd0b145509ea4a67df1
@@ -126,7 +126,7 @@ module ForemanPuppet
126
126
 
127
127
  def get_environment_for(host, id)
128
128
  if id == 'inherit' && host.hostgroup.present?
129
- host.hostgroup.environment
129
+ host.hostgroup.puppet&.environment
130
130
  else
131
131
  ForemanPuppet::Environment.find_by(id: id)
132
132
  end
@@ -3,7 +3,9 @@ module ForemanPuppet
3
3
  module V2
4
4
  class HostsBulkActionsController < ::ForemanPuppet::Api::V2::PuppetBaseController
5
5
  include ::Api::V2::BulkHostsExtension
6
- before_action :find_editable_hosts, only: %i[change_puppet_proxy remove_puppet_proxy]
6
+ before_action :find_editable_hosts, only: %i[change_puppet_environment change_puppet_proxy remove_puppet_proxy]
7
+ before_action :find_environment, only: %i[change_puppet_environment]
8
+ before_action :validate_environment_taxonomies, only: %i[change_puppet_environment]
7
9
  before_action :find_smart_proxy, only: %i[change_puppet_proxy]
8
10
 
9
11
  def_param_group :bulk_params do
@@ -17,6 +19,27 @@ module ForemanPuppet
17
19
  end
18
20
  end
19
21
 
22
+ api :PUT, '/hosts/bulk/change_puppet_environment', N_('Change Puppet environment')
23
+ param_group :bulk_params
24
+ param :environment_id, String, required: false, desc: N_('ID of the Puppet environment to set for the selected hosts')
25
+ def change_puppet_environment
26
+ error_hosts = ::BulkHostsManager.new(hosts: @hosts).change_puppet_environment(resolved_environment)
27
+
28
+ process_bulk_response(
29
+ error_hosts,
30
+ success_message: format(n_(
31
+ 'Updated host: changed environment',
32
+ 'Updated hosts: changed environment',
33
+ @hosts.count
34
+ )),
35
+ error_message: format(n_(
36
+ 'Failed to change environment for %{count} host',
37
+ 'Failed to change environment for %{count} hosts',
38
+ error_hosts.count
39
+ ), count: error_hosts.count)
40
+ )
41
+ end
42
+
20
43
  api :PUT, '/hosts/bulk/change_puppet_proxy', N_('Change Puppet (CA) Proxy')
21
44
  param_group :bulk_params
22
45
  param :proxy_id, :number, required: true, desc: N_('ID of the Puppet proxy to reassign the hosts to')
@@ -65,6 +88,14 @@ module ForemanPuppet
65
88
  end
66
89
 
67
90
  def process_bulk_puppet_proxy_response(error_hosts, success_message:, error_message:)
91
+ process_bulk_response(
92
+ error_hosts,
93
+ success_message: success_message,
94
+ error_message: error_message
95
+ )
96
+ end
97
+
98
+ def process_bulk_response(error_hosts, success_message:, error_message:)
68
99
  if error_hosts.empty?
69
100
  process_response(true, { message: success_message })
70
101
  else
@@ -93,6 +124,49 @@ module ForemanPuppet
93
124
  Foreman::Cast.to_bool(params[:ca_proxy])
94
125
  end
95
126
 
127
+ def find_environment
128
+ return true if environment_value == 'inherit' || environment_value.blank?
129
+
130
+ @environment = ForemanPuppet::Environment.find_by(id: environment_value)
131
+ return true if @environment.present?
132
+
133
+ render json: {
134
+ error: {
135
+ message: format(_('A Puppet environment with id %{id} could not be found.'), id: environment_value),
136
+ },
137
+ }, status: :unprocessable_entity
138
+ false
139
+ end
140
+
141
+ def validate_environment_taxonomies
142
+ return true if resolved_environment.blank? || resolved_environment == 'inherit'
143
+
144
+ invalid_host_ids = @hosts.reject do |host|
145
+ ForemanPuppet::Environment.with_taxonomy_scope(host.organization, host.location)
146
+ .exists?(id: resolved_environment.id)
147
+ end.map(&:id)
148
+
149
+ return true if invalid_host_ids.empty?
150
+
151
+ render_error(:bulk_hosts_error, status: :unprocessable_entity,
152
+ locals: {
153
+ message: _('Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.'),
154
+ failed_host_ids: invalid_host_ids,
155
+ })
156
+ false
157
+ end
158
+
159
+ def environment_value
160
+ params[:environment_id]
161
+ end
162
+
163
+ def resolved_environment
164
+ return 'inherit' if environment_value == 'inherit'
165
+ return nil if environment_value.blank?
166
+
167
+ @environment
168
+ end
169
+
96
170
  def proxy_type
97
171
  ca_proxy? ? _('Puppet CA proxy') : _('Puppet proxy')
98
172
  end
@@ -13,9 +13,9 @@ module ForemanPuppet
13
13
  include_in_clone puppet: %i[config_groups host_config_groups host_classes]
14
14
 
15
15
  scoped_search relation: :environment, on: :name, complete_value: true, rename: :environment
16
- scoped_search relation: :puppetclasses, on: :name, complete_value: true, rename: :puppetclass, only_explicit: true, operators: ['= ', '~ '],
16
+ scoped_search relation: :search_puppetclasses, on: :name, complete_value: true, rename: :puppetclass, only_explicit: true, operators: ['= ', '~ '],
17
17
  ext_method: :search_by_puppetclass
18
- scoped_search relation: :config_groups, on: :name, complete_value: true, rename: :config_group, only_explicit: true, operators: ['= ', '~ '],
18
+ scoped_search relation: :search_config_groups, on: :name, complete_value: true, rename: :config_group, only_explicit: true, operators: ['= ', '~ '],
19
19
  ext_method: :search_by_config_group
20
20
  end
21
21
 
@@ -3,6 +3,41 @@ module ForemanPuppet
3
3
  module HostCommon
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ ASSIGNMENT_RELATIONS = %i[search_puppetclasses search_config_groups].freeze
7
+
8
+ included do
9
+ singleton_class.prepend CompletionMethods
10
+
11
+ has_many :search_puppetclasses, through: :puppet, source: :puppetclasses, class_name: '::ForemanPuppet::Puppetclass'
12
+ has_many :search_config_groups, through: :puppet, source: :config_groups, class_name: '::ForemanPuppet::ConfigGroup'
13
+ end
14
+
15
+ module CompletionMethods
16
+ def complete_for(query, options = {})
17
+ return super if query.nil?
18
+
19
+ completer = AssignmentCompleter.new(scoped_search_definition, query, options)
20
+ return super unless completer.assignment_field?
21
+
22
+ completer.build_autocomplete_options
23
+ end
24
+ end
25
+
26
+ class AssignmentCompleter < ScopedSearch::AutoCompleteBuilder
27
+ def assignment_field?
28
+ return false unless complete_options(last_node).include?(:value)
29
+
30
+ token = last_token_is(COMPARISON_OPERATORS) ? tokens[-2] : tokens[-3]
31
+ ASSIGNMENT_RELATIONS.include?(definition.field_by_name(token)&.relation)
32
+ end
33
+
34
+ def completer_scope(field)
35
+ return super unless ASSIGNMENT_RELATIONS.include?(field.relation)
36
+
37
+ field.klass.completer_scope(@options).reorder(Arel.sql(field.quoted_field))
38
+ end
39
+ end
40
+
6
41
  def all_puppetclasses(env = environment)
7
42
  return ForemanPuppet::Puppetclass.none unless puppet
8
43
  puppet.all_puppetclasses(env)
@@ -16,13 +16,13 @@ module ForemanPuppet
16
16
 
17
17
  # will need through relation to work properly
18
18
  scoped_search relation: :environment, on: :name, complete_value: true, rename: :environment, only_explicit: true
19
- scoped_search relation: :puppetclasses, on: :name,
19
+ scoped_search relation: :search_puppetclasses, on: :name,
20
20
  complete_value: true,
21
21
  rename: :puppetclass,
22
22
  only_explicit: true,
23
23
  operators: ['= ', '~ '],
24
24
  ext_method: :search_by_puppetclass
25
- scoped_search relation: :config_groups, on: :name,
25
+ scoped_search relation: :search_config_groups, on: :name,
26
26
  complete_value: true,
27
27
  rename: :config_group,
28
28
  only_explicit: true,
@@ -45,9 +45,16 @@ module ForemanPuppet
45
45
  def search_by_puppetclass(_key, operator, value)
46
46
  conditions = sanitize_sql_for_conditions(["puppetclasses.name #{operator} ?", value_to_sql(operator, value)])
47
47
  hostgroup_ids = ::Hostgroup.joins(puppet: :puppetclasses).where(conditions).map(&:subtree_ids)
48
+ config_group_ids = ForemanPuppet::ConfigGroup.joins(:puppetclasses).where(conditions).pluck(:id)
49
+ if config_group_ids.any?
50
+ hostgroup_cg_ids = ForemanPuppet::HostgroupPuppetFacet.joins(:host_config_groups)
51
+ .where(host_config_groups: { config_group_id: config_group_ids })
52
+ .pluck(:hostgroup_id)
53
+ hostgroup_ids += ::Hostgroup.where(id: hostgroup_cg_ids).map(&:subtree_ids)
54
+ end
48
55
 
49
56
  conds = []
50
- conds << "hostgroups.id IN (#{hostgroup_ids.join(',')})" if hostgroup_ids.present?
57
+ conds << "hostgroups.id IN (#{hostgroup_ids.uniq.join(',')})" if hostgroup_ids.present?
51
58
 
52
59
  { conditions: conds.join(' OR ').presence || 'hostgroups.id < 0' }
53
60
  end
@@ -16,6 +16,8 @@ module ForemanPuppet
16
16
 
17
17
  validates :name, presence: true, uniqueness: true
18
18
 
19
+ scope :assigned_to_hosts, -> { where(id: ForemanPuppet::HostConfigGroup.select(:config_group_id)) }
20
+
19
21
  scoped_search on: :name, complete_value: true
20
22
  scoped_search relation: :puppetclasses, on: :name, complete_value: true, rename: :puppetclass, only_explicit: true, operators: ['= ', '~ ']
21
23
 
@@ -41,5 +43,9 @@ module ForemanPuppet
41
43
  def hostgroups_count
42
44
  ::Hostgroup.authorized.search_for(%(config_group="#{name}")).size
43
45
  end
46
+
47
+ def self.completer_scope(options)
48
+ super.merge(assigned_to_hosts)
49
+ end
44
50
  end
45
51
  end
@@ -41,6 +41,16 @@ module ForemanPuppet
41
41
 
42
42
  default_scope -> { order(:name) }
43
43
 
44
+ scope :assigned_to_hosts, lambda {
45
+ direct = ForemanPuppet::HostClass.select(:puppetclass_id)
46
+ via_hostgroup = ForemanPuppet::HostgroupClass.select(:puppetclass_id)
47
+ via_config_group = ForemanPuppet::ConfigGroupClass
48
+ .where(config_group_id: ForemanPuppet::HostConfigGroup.select(:config_group_id))
49
+ .select(:puppetclass_id)
50
+
51
+ where(id: direct).or(where(id: via_hostgroup)).or(where(id: via_config_group))
52
+ }
53
+
44
54
  scoped_search on: :name, complete_value: true
45
55
  scoped_search relation: :environments, on: :name, complete_value: true, rename: 'environment'
46
56
  scoped_search relation: :organizations, on: :name, complete_value: true, rename: 'organization', only_explicit: true
@@ -142,5 +152,9 @@ module ForemanPuppet
142
152
  puppet_classes = (direct + indirect).uniq
143
153
  { conditions: "puppetclasses.id IN(#{puppet_classes.join(',')})" }
144
154
  end
155
+
156
+ def self.completer_scope(options)
157
+ super.merge(assigned_to_hosts)
158
+ end
145
159
  end
146
160
  end
@@ -3,6 +3,24 @@ module ForemanPuppet
3
3
  module BulkHostsManager
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ def change_puppet_environment(environment)
7
+ error_hosts = []
8
+ @hosts.each do |host|
9
+ puppet = host.puppet || host.build_puppet
10
+ puppet.environment = if environment == 'inherit'
11
+ host.hostgroup&.puppet&.environment
12
+ else
13
+ environment
14
+ end
15
+ host.save(validate: false)
16
+ rescue StandardError => e
17
+ message = format(_('Failed to set Puppet environment for %{host}.'), host: host)
18
+ Foreman::Logging.exception(message, e)
19
+ error_hosts << host.id
20
+ end
21
+ error_hosts
22
+ end
23
+
6
24
  def change_puppet_proxy(proxy, is_ca_proxy)
7
25
  error_hosts = []
8
26
  @hosts.each do |host|
data/config/api_routes.rb CHANGED
@@ -4,6 +4,7 @@ Foreman::Application.routes.draw do
4
4
  scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v1|v2/, constraints: ApiConstraints.new(version: 2, default: true) do
5
5
  match 'hosts/bulk/change_puppet_proxy', to: 'hosts_bulk_actions#change_puppet_proxy', via: [:put]
6
6
  match 'hosts/bulk/remove_puppet_proxy', to: 'hosts_bulk_actions#remove_puppet_proxy', via: [:put]
7
+ match 'hosts/bulk/change_puppet_environment', to: 'hosts_bulk_actions#change_puppet_environment', via: [:put]
7
8
  end
8
9
  end
9
10
  end
@@ -4,5 +4,5 @@ proxy_features = %w[Puppet]
4
4
 
5
5
  proxy_features.each do |f_name|
6
6
  f = Feature.where(name: f_name).first_or_create
7
- raise "Unable to create proxy feature: #{format_errors f}" if f.nil? || f.errors.any?
7
+ raise "Unable to create proxy feature: #{SeedHelper.format_errors(f)}" if f.nil? || f.errors.any?
8
8
  end
@@ -1,5 +1,5 @@
1
1
  Foreman::Plugin.register :foreman_puppet do
2
- requires_foreman '>= 3.19'
2
+ requires_foreman '>= 5.0'
3
3
  register_gettext
4
4
 
5
5
  # Add Global JS file for extending foreman-core components and routes
@@ -62,6 +62,7 @@ Foreman::Plugin.register :foreman_puppet do
62
62
  p.actions << 'hosts/update_multiple_environment'
63
63
  p.actions << 'hosts/select_multiple_puppet_proxy'
64
64
  p.actions << 'hosts/update_multiple_puppet_proxy'
65
+ p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_puppet_environment'
65
66
  p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_puppet_proxy'
66
67
  p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/remove_puppet_proxy'
67
68
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanPuppet
2
- VERSION = '10.1.3'.freeze
2
+ VERSION = '11.1.0'.freeze
3
3
  end
@@ -16,8 +16,101 @@ module ForemanPuppet
16
16
  organization: host.organization,
17
17
  location: host.location)
18
18
  end
19
+ let(:environment) { FactoryBot.create(:environment, organizations: [host.organization], locations: [host.location]) }
20
+ let(:hostgroup_environment) { FactoryBot.create(:environment, organizations: [host.organization], locations: [host.location]) }
21
+ let(:hostgroup) do
22
+ FactoryBot.create(:hostgroup, :with_puppet_enc,
23
+ environment: hostgroup_environment,
24
+ organizations: [host.organization],
25
+ locations: [host.location])
26
+ end
19
27
  let(:proxy) { FactoryBot.create(:puppet_and_ca_smart_proxy, organizations: [host.organization], locations: [host.location]) }
20
28
 
29
+ def put_change_puppet_environment(params:, session: nil)
30
+ put :change_puppet_environment, params: params, session: session
31
+ end
32
+
33
+ test 'changes puppet environment for selected hosts' do
34
+ put_change_puppet_environment(params: bulk_params.merge(environment_id: environment.id))
35
+
36
+ assert_response :success
37
+ assert_equal environment.id, host2.reload.puppet.environment_id
38
+ assert_equal environment.id, host.reload.puppet.environment_id
39
+ end
40
+
41
+ test 'inherits puppet environment from hostgroups for selected hosts' do
42
+ host.update!(hostgroup: hostgroup)
43
+ host2.update!(hostgroup: hostgroup)
44
+
45
+ put_change_puppet_environment(params: bulk_params.merge(environment_id: 'inherit'))
46
+
47
+ assert_response :success
48
+ assert_equal hostgroup_environment.id, host2.reload.puppet.environment_id
49
+ assert_equal hostgroup_environment.id, host.reload.puppet.environment_id
50
+ end
51
+
52
+ test 'clears puppet environment for selected hosts' do
53
+ host.puppet.update!(environment: environment)
54
+ host2.puppet.update!(environment: environment)
55
+
56
+ put_change_puppet_environment(
57
+ params: bulk_params.merge(environment_id: nil),
58
+ session: set_session_user
59
+ )
60
+
61
+ assert_response :success
62
+ assert_nil host.reload.puppet.environment
63
+ assert_nil host2.reload.puppet.environment
64
+ end
65
+
66
+ test 'returns error when puppet environment is missing' do
67
+ missing_environment_id = 999_999
68
+
69
+ put_change_puppet_environment(
70
+ params: bulk_params.merge(environment_id: missing_environment_id),
71
+ session: set_session_user
72
+ )
73
+
74
+ assert_response :unprocessable_entity
75
+ response = JSON.parse(@response.body)
76
+ assert_equal "A Puppet environment with id #{missing_environment_id} could not be found.",
77
+ response.dig('error', 'message')
78
+ end
79
+
80
+ test 'returns error when changing puppet environment fails for some hosts' do
81
+ ::BulkHostsManager.any_instance.expects(:change_puppet_environment)
82
+ .with(environment)
83
+ .returns([host2.id])
84
+
85
+ put_change_puppet_environment(
86
+ params: bulk_params.merge(environment_id: environment.id),
87
+ session: set_session_user
88
+ )
89
+
90
+ assert_response :unprocessable_entity
91
+ response = JSON.parse(@response.body)
92
+ assert_equal 'Failed to change environment for 1 host',
93
+ response.dig('error', 'message')
94
+ assert_equal [host2.id], response.dig('error', 'failed_host_ids')
95
+ end
96
+
97
+ test 'returns error when puppet environment is outside host taxonomies' do
98
+ invalid_environment = FactoryBot.create(:environment,
99
+ organizations: [host.organization],
100
+ locations: [FactoryBot.create(:location)])
101
+
102
+ put_change_puppet_environment(
103
+ params: bulk_params.merge(environment_id: invalid_environment.id),
104
+ session: set_session_user
105
+ )
106
+
107
+ assert_response :unprocessable_entity
108
+ response = JSON.parse(@response.body)
109
+ assert_equal 'Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.',
110
+ response.dig('error', 'message')
111
+ assert_equal [host.id, host2.id].sort, response.dig('error', 'failed_host_ids').sort
112
+ end
113
+
21
114
  test 'changes puppet proxy for selected hosts' do
22
115
  put :change_puppet_proxy,
23
116
  params: bulk_params.merge(proxy_id: proxy.id, ca_proxy: false)
@@ -117,6 +117,72 @@ module ForemanPuppet
117
117
  end
118
118
  end
119
119
 
120
+ describe '#complete_for' do
121
+ let(:host) { FactoryBot.create(:host, :with_puppet_enc, :with_puppetclass) }
122
+
123
+ test 'can complete Puppet class values' do
124
+ puppetclass = host.puppet.puppetclasses.first
125
+ completions = ::Host::Managed.complete_for("puppetclass = #{puppetclass.name}")
126
+
127
+ assert_includes completions, %(puppetclass = "#{puppetclass.name}")
128
+ end
129
+
130
+ test 'can complete config group values' do
131
+ config_group = FactoryBot.create(:config_group)
132
+ host.puppet.config_groups << config_group
133
+ completions = ::Host::Managed.complete_for("config_group = #{config_group.name}")
134
+
135
+ assert_includes completions, %(config_group = "#{config_group.name}")
136
+ end
137
+
138
+ test 'can complete Puppet class values inherited from a hostgroup' do
139
+ hostgroup = FactoryBot.create(:hostgroup, :with_puppet_enc, :with_puppetclass)
140
+ FactoryBot.create(:host, hostgroup: hostgroup)
141
+ puppetclass = hostgroup.puppet.puppetclasses.first
142
+ completions = ::Host::Managed.complete_for("puppetclass = #{puppetclass.name}")
143
+
144
+ assert_includes completions, %(puppetclass = "#{puppetclass.name}")
145
+ end
146
+
147
+ test 'can complete Puppet class values assigned through a config group' do
148
+ config_group = FactoryBot.create(:config_group, :with_puppetclass)
149
+ host.puppet.config_groups << config_group
150
+ puppetclass = config_group.puppetclasses.first
151
+ completions = ::Host::Managed.complete_for("puppetclass = #{puppetclass.name}")
152
+
153
+ assert_includes completions, %(puppetclass = "#{puppetclass.name}")
154
+ end
155
+
156
+ test 'can complete config group values inherited from a hostgroup' do
157
+ hostgroup = FactoryBot.create(:hostgroup, :with_puppet_enc, :with_config_group)
158
+ FactoryBot.create(:host, hostgroup: hostgroup)
159
+ config_group = hostgroup.puppet.config_groups.first
160
+ completions = ::Host::Managed.complete_for("config_group = #{config_group.name}")
161
+
162
+ assert_includes completions, %(config_group = "#{config_group.name}")
163
+ end
164
+
165
+ test 'does not complete unassigned Puppet class values' do
166
+ puppetclass = FactoryBot.create(:puppetclass)
167
+ completions = ::Host::Managed.complete_for("puppetclass = #{puppetclass.name}")
168
+
169
+ assert_not_includes completions, %(puppetclass = "#{puppetclass.name}")
170
+ end
171
+
172
+ test 'does not complete unassigned config group values' do
173
+ config_group = FactoryBot.create(:config_group)
174
+ completions = ::Host::Managed.complete_for("config_group = #{config_group.name}")
175
+
176
+ assert_not_includes completions, %(config_group = "#{config_group.name}")
177
+ end
178
+
179
+ test 'preserves core host value completions' do
180
+ completions = as_admin { ::Host::Managed.complete_for('user.login =') }
181
+
182
+ assert_includes completions, 'user.login = current_user'
183
+ end
184
+ end
185
+
120
186
  describe '#info puppet bits' do
121
187
  test 'ENC YAML omits environment if no puppet facet' do
122
188
  host = FactoryBot.build_stubbed(:host)
@@ -22,5 +22,37 @@ module ForemanPuppet
22
22
  assert_includes hostgroup.puppet.puppetclasses, puppet_class
23
23
  assert_includes hostgroup.puppetclasses, puppet_class
24
24
  end
25
+
26
+ test 'can complete Puppet class values' do
27
+ puppetclass = FactoryBot.create(:puppetclass)
28
+ hostgroup.puppet.puppetclasses << puppetclass
29
+ completions = ::Hostgroup.complete_for("puppetclass = #{puppetclass.name}")
30
+
31
+ assert_includes completions, %(puppetclass = "#{puppetclass.name}")
32
+ end
33
+
34
+ test 'can complete config group values' do
35
+ config_group = hostgroup.puppet.config_groups.first
36
+ completions = ::Hostgroup.complete_for("config_group = #{config_group.name}")
37
+
38
+ assert_includes completions, %(config_group = "#{config_group.name}")
39
+ end
40
+
41
+ test 'can complete Puppet class values inherited from a parent hostgroup' do
42
+ parent = FactoryBot.create(:hostgroup, :with_puppet_enc, :with_puppetclass)
43
+ FactoryBot.create(:hostgroup, parent: parent)
44
+ puppetclass = parent.puppet.puppetclasses.first
45
+ completions = ::Hostgroup.complete_for("puppetclass = #{puppetclass.name}")
46
+
47
+ assert_includes completions, %(puppetclass = "#{puppetclass.name}")
48
+ end
49
+
50
+ test 'searches Puppet class values assigned through a config group' do
51
+ config_group = hostgroup.puppet.config_groups.first
52
+ puppetclass = FactoryBot.create(:puppetclass)
53
+ config_group.puppetclasses << puppetclass
54
+
55
+ assert_includes ::Hostgroup.search_for("puppetclass = #{puppetclass.name}"), hostgroup
56
+ end
25
57
  end
26
58
  end
@@ -4,6 +4,51 @@ class BulkHostsManagerTest < ActiveSupport::TestCase
4
4
  let(:hosts) { FactoryBot.create_list(:host, 2, :with_puppet_enc) }
5
5
  let(:manager) { ::BulkHostsManager.new(hosts: hosts) }
6
6
  let(:proxy) { FactoryBot.create(:puppet_smart_proxy) }
7
+ let(:environment) { FactoryBot.create(:environment, organizations: [hosts.first.organization], locations: [hosts.first.location]) }
8
+
9
+ test 'changes puppet environment for hosts' do
10
+ manager.change_puppet_environment(environment)
11
+
12
+ hosts.each do |host|
13
+ assert_equal environment.id, host.reload.puppet.environment_id
14
+ end
15
+ end
16
+
17
+ test 'inherits puppet environment from hostgroup' do
18
+ organization = FactoryBot.create(:organization)
19
+ location = FactoryBot.create(:location)
20
+ inherited_environment = FactoryBot.create(:environment, organizations: [organization], locations: [location])
21
+ initial_environment = FactoryBot.create(:environment, organizations: [organization], locations: [location])
22
+ hostgroup = FactoryBot.create(:hostgroup, :with_puppet_enc,
23
+ environment: inherited_environment,
24
+ organizations: [organization],
25
+ locations: [location])
26
+ inherited_hosts = FactoryBot.create_list(:host, 2, :with_puppet_enc,
27
+ environment: initial_environment,
28
+ hostgroup: hostgroup,
29
+ organization: organization,
30
+ location: location)
31
+
32
+ inherited_hosts.each do |host|
33
+ assert_not_equal inherited_environment.id, host.reload.puppet.environment_id
34
+ end
35
+
36
+ ::BulkHostsManager.new(hosts: inherited_hosts).change_puppet_environment('inherit')
37
+
38
+ inherited_hosts.each do |host|
39
+ assert_equal inherited_environment.id, host.reload.puppet.environment_id
40
+ end
41
+ end
42
+
43
+ test 'clears puppet environment when environment is nil' do
44
+ hosts.each { |host| host.puppet.update!(environment: environment) }
45
+
46
+ manager.change_puppet_environment(nil)
47
+
48
+ hosts.each do |host|
49
+ assert_nil host.reload.puppet.environment
50
+ end
51
+ end
7
52
 
8
53
  test 'changes puppet proxy for hosts' do
9
54
  manager.change_puppet_proxy(proxy, false)
@@ -6,5 +6,13 @@ export const API = {
6
6
  patch: jest.fn(),
7
7
  };
8
8
 
9
+ export const APIActions = {
10
+ get: jest.fn(),
11
+ post: jest.fn(),
12
+ put: jest.fn(),
13
+ patch: jest.fn(),
14
+ delete: jest.fn(),
15
+ };
16
+
9
17
  export const get = data => ({ type: 'get-some-type', ...data });
10
18
  export const post = data => ({ type: 'post-some-type', ...data });
@@ -7,12 +7,15 @@ import reducers from './src/reducers';
7
7
  import { registerFills } from './src/Extends/Fills';
8
8
  import { registerLegacy } from './legacy';
9
9
  import HostsIndexActionsBar from './src/Extends/Hosts/ActionsBar';
10
+ import BulkChangePuppetEnvironment from './src/Extends/Hosts/BulkActions/BulkChangePuppetEnvironment';
11
+ import BulkRemovePuppetEnvironment from './src/Extends/Hosts/BulkActions/BulkRemovePuppetEnvironment';
10
12
  import BulkChangePuppetProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetProxy';
11
13
  import BulkChangePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy';
12
14
  import BulkRemovePuppetProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy';
13
15
  import BulkRemovePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy';
14
16
 
15
17
  const GLOBAL_FILL_PRIORITY = 100;
18
+ const PUPPET_ENV_COLUMN_WEIGHT = 2700;
16
19
 
17
20
  // register reducers
18
21
  registerReducer('puppet', reducers);
@@ -28,7 +31,7 @@ const puppetHostsIndexColumns = [
28
31
  title: __('Puppet env'),
29
32
  isSorted: true,
30
33
  wrapper: hostDetails => hostDetails.environment_name,
31
- weight: 2700,
34
+ weight: PUPPET_ENV_COLUMN_WEIGHT,
32
35
  },
33
36
  ];
34
37
 
@@ -45,6 +48,20 @@ addGlobalFill(
45
48
  GLOBAL_FILL_PRIORITY
46
49
  );
47
50
 
51
+ addGlobalFill(
52
+ '_all-hosts-modals',
53
+ 'BulkChangePuppetEnvironment',
54
+ <BulkChangePuppetEnvironment key="bulk-change-puppet-environment" />,
55
+ GLOBAL_FILL_PRIORITY
56
+ );
57
+
58
+ addGlobalFill(
59
+ '_all-hosts-modals',
60
+ 'BulkRemovePuppetEnvironment',
61
+ <BulkRemovePuppetEnvironment key="bulk-remove-puppet-environment" />,
62
+ GLOBAL_FILL_PRIORITY
63
+ );
64
+
48
65
  addGlobalFill(
49
66
  '_all-hosts-modals',
50
67
  'BulkChangePuppetProxy',