Skip to content

Commit 6cb72e8

Browse files
committed
Add Puppet environment bulk actions
Generated-by: OpenAI Codex <codex@openai.com>
1 parent 16ea7b4 commit 6cb72e8

16 files changed

Lines changed: 1090 additions & 3 deletions

File tree

app/controllers/concerns/foreman_puppet/extensions/hosts_controller_extensions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def get_environment_id(env_params)
126126

127127
def get_environment_for(host, id)
128128
if id == 'inherit' && host.hostgroup.present?
129-
host.hostgroup.environment
129+
host.hostgroup.puppet&.environment
130130
else
131131
ForemanPuppet::Environment.find_by(id: id)
132132
end

app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ module Api
33
module V2
44
class HostsBulkActionsController < ::ForemanPuppet::Api::V2::PuppetBaseController
55
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_environment change_puppet_proxy remove_puppet_proxy]
7+
before_action :find_environment, only: %i[change_environment]
8+
before_action :validate_environment_taxonomies, only: %i[change_environment]
79
before_action :find_smart_proxy, only: %i[change_puppet_proxy]
810

911
def_param_group :bulk_params do
@@ -17,6 +19,27 @@ class HostsBulkActionsController < ::ForemanPuppet::Api::V2::PuppetBaseControlle
1719
end
1820
end
1921

22+
api :PUT, '/foreman_puppet/api/v2/hosts/bulk/change_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_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+
2043
api :PUT, '/hosts/bulk/change_puppet_proxy', N_('Change Puppet (CA) Proxy')
2144
param_group :bulk_params
2245
param :proxy_id, :number, required: true, desc: N_('ID of the Puppet proxy to reassign the hosts to')
@@ -65,6 +88,14 @@ def find_editable_hosts
6588
end
6689

6790
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:)
6899
if error_hosts.empty?
69100
process_response(true, { message: success_message })
70101
else
@@ -93,6 +124,50 @@ def ca_proxy?
93124
Foreman::Cast.to_bool(params[:ca_proxy])
94125
end
95126

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+
.where(id: resolved_environment.id)
147+
.exists?
148+
end.map(&:id)
149+
150+
return true if invalid_host_ids.empty?
151+
152+
render_error(:bulk_hosts_error, status: :unprocessable_entity,
153+
locals: {
154+
message: _('Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.'),
155+
failed_host_ids: invalid_host_ids,
156+
})
157+
false
158+
end
159+
160+
def environment_value
161+
params[:environment_id]
162+
end
163+
164+
def resolved_environment
165+
return 'inherit' if environment_value == 'inherit'
166+
return nil if environment_value.blank?
167+
168+
@environment
169+
end
170+
96171
def proxy_type
97172
ca_proxy? ? _('Puppet CA proxy') : _('Puppet proxy')
98173
end

app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@ module Extensions
33
module BulkHostsManager
44
extend ActiveSupport::Concern
55

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+
624
def change_puppet_proxy(proxy, is_ca_proxy)
725
error_hosts = []
826
@hosts.each do |host|

config/api_routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
ForemanPuppet::Engine.routes.draw do
1313
namespace :api, defaults: { format: 'json' } do
1414
scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v1|v2/, constraints: ApiConstraints.new(version: 2, default: true) do
15+
match 'hosts/bulk/change_environment', to: 'hosts_bulk_actions#change_environment', via: [:put]
16+
1517
constraints(id: %r{[^/]+}) do
1618
resources :config_groups, except: %i[new edit]
1719

lib/foreman_puppet/register.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
p.actions << 'hosts/update_multiple_environment'
6363
p.actions << 'hosts/select_multiple_puppet_proxy'
6464
p.actions << 'hosts/update_multiple_puppet_proxy'
65+
p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_environment'
6566
p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/change_puppet_proxy'
6667
p.actions << 'foreman_puppet/api/v2/hosts_bulk_actions/remove_puppet_proxy'
6768
end

test/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller_test.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,95 @@ class HostsBulkActionsControllerTest < ActionController::TestCase
1616
organization: host.organization,
1717
location: host.location)
1818
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
1927
let(:proxy) { FactoryBot.create(:puppet_and_ca_smart_proxy, organizations: [host.organization], locations: [host.location]) }
2028

29+
test 'changes puppet environment for selected hosts' do
30+
put :change_environment,
31+
params: bulk_params.merge(environment_id: environment.id)
32+
33+
assert_response :success
34+
assert_equal environment.id, host2.reload.puppet.environment_id
35+
assert_equal environment.id, host.reload.puppet.environment_id
36+
end
37+
38+
test 'inherits puppet environment from hostgroups for selected hosts' do
39+
host.update!(hostgroup: hostgroup)
40+
host2.update!(hostgroup: hostgroup)
41+
42+
put :change_environment,
43+
params: bulk_params.merge(environment_id: 'inherit')
44+
45+
assert_response :success
46+
assert_equal hostgroup_environment.id, host2.reload.puppet.environment_id
47+
assert_equal hostgroup_environment.id, host.reload.puppet.environment_id
48+
end
49+
50+
test 'clears puppet environment for selected hosts' do
51+
host.puppet.update!(environment: environment)
52+
host2.puppet.update!(environment: environment)
53+
54+
put :change_environment,
55+
params: bulk_params.merge(environment_id: nil),
56+
session: set_session_user
57+
58+
assert_response :success
59+
assert_nil host.reload.puppet.environment
60+
assert_nil host2.reload.puppet.environment
61+
end
62+
63+
test 'returns error when puppet environment is missing' do
64+
missing_environment_id = 999_999
65+
66+
put :change_environment,
67+
params: bulk_params.merge(environment_id: missing_environment_id),
68+
session: set_session_user
69+
70+
assert_response :unprocessable_entity
71+
response = JSON.parse(@response.body)
72+
assert_equal "A Puppet environment with id #{missing_environment_id} could not be found.",
73+
response.dig('error', 'message')
74+
end
75+
76+
test 'returns error when changing puppet environment fails for some hosts' do
77+
::BulkHostsManager.any_instance.expects(:change_puppet_environment)
78+
.with(environment)
79+
.returns([host2.id])
80+
81+
put :change_environment,
82+
params: bulk_params.merge(environment_id: environment.id),
83+
session: set_session_user
84+
85+
assert_response :unprocessable_entity
86+
response = JSON.parse(@response.body)
87+
assert_equal 'Failed to change environment for 1 host',
88+
response.dig('error', 'message')
89+
assert_equal [host2.id], response.dig('error', 'failed_host_ids')
90+
end
91+
92+
test 'returns error when puppet environment is outside host taxonomies' do
93+
invalid_environment = FactoryBot.create(:environment,
94+
organizations: [FactoryBot.create(:organization)],
95+
locations: [FactoryBot.create(:location)])
96+
97+
put :change_environment,
98+
params: bulk_params.merge(environment_id: invalid_environment.id),
99+
session: set_session_user
100+
101+
assert_response :unprocessable_entity
102+
response = JSON.parse(@response.body)
103+
assert_equal 'Selected Puppet environment is not assigned to the proper organization and/or location for all hosts.',
104+
response.dig('error', 'message')
105+
assert_equal [host.id, host2.id].sort, response.dig('error', 'failed_host_ids').sort
106+
end
107+
21108
test 'changes puppet proxy for selected hosts' do
22109
put :change_puppet_proxy,
23110
params: bulk_params.merge(proxy_id: proxy.id, ca_proxy: false)

test/services/foreman_puppet/bulk_hosts_manager_test.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@ class BulkHostsManagerTest < ActiveSupport::TestCase
44
let(:hosts) { FactoryBot.create_list(:host, 2, :with_puppet_enc) }
55
let(:manager) { ::BulkHostsManager.new(hosts: hosts) }
66
let(:proxy) { FactoryBot.create(:puppet_smart_proxy) }
7+
let(:environment) { FactoryBot.create(:environment) }
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+
inherited_environment = FactoryBot.create(:environment)
19+
hostgroup = FactoryBot.create(:hostgroup, :with_puppet_enc, environment: inherited_environment)
20+
inherited_hosts = FactoryBot.create_list(:host, 2, :with_puppet_enc, hostgroup: hostgroup)
21+
22+
::BulkHostsManager.new(hosts: inherited_hosts).change_puppet_environment('inherit')
23+
24+
inherited_hosts.each do |host|
25+
assert_equal inherited_environment.id, host.reload.puppet.environment_id
26+
end
27+
end
28+
29+
test 'clears puppet environment when environment is nil' do
30+
hosts.each { |host| host.puppet.update!(environment: environment) }
31+
32+
manager.change_puppet_environment(nil)
33+
34+
hosts.each do |host|
35+
assert_nil host.reload.puppet.environment
36+
end
37+
end
738

839
test 'changes puppet proxy for hosts' do
940
manager.change_puppet_proxy(proxy, false)

webpack/global_index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import reducers from './src/reducers';
77
import { registerFills } from './src/Extends/Fills';
88
import { registerLegacy } from './legacy';
99
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';
1012
import BulkChangePuppetProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetProxy';
1113
import BulkChangePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy';
1214
import BulkRemovePuppetProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy';
@@ -43,6 +45,20 @@ addGlobalFill(
4345
100
4446
);
4547

48+
addGlobalFill(
49+
'_all-hosts-modals',
50+
'BulkChangePuppetEnvironment',
51+
<BulkChangePuppetEnvironment key="bulk-change-puppet-environment" />,
52+
100
53+
);
54+
55+
addGlobalFill(
56+
'_all-hosts-modals',
57+
'BulkRemovePuppetEnvironment',
58+
<BulkRemovePuppetEnvironment key="bulk-remove-puppet-environment" />,
59+
100
60+
);
61+
4662
addGlobalFill(
4763
'_all-hosts-modals',
4864
'BulkChangePuppetProxy',

webpack/src/Extends/Hosts/ActionsBar/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ const HostActionsBar = () => {
2424
<Menu ouiaId="content-flyout-menu" onSelect={() => setMenuOpen(false)}>
2525
<MenuContent>
2626
<MenuList>
27+
<MenuItem
28+
itemId="bulk-change-puppet-environment-menu-item"
29+
key="bulk-change-puppet-environment-menu-item"
30+
onClick={() =>
31+
handleOpenBulkModal('bulk-change-puppet-environment')
32+
}
33+
isDisabled={selectedCount === 0}
34+
>
35+
{__('Change Puppet environment')}
36+
</MenuItem>
37+
<MenuItem
38+
itemId="bulk-remove-puppet-environment-menu-item"
39+
key="bulk-remove-puppet-environment-menu-item"
40+
onClick={() =>
41+
handleOpenBulkModal('bulk-remove-puppet-environment')
42+
}
43+
isDisabled={selectedCount === 0}
44+
>
45+
{__('Remove Puppet environment')}
46+
</MenuItem>
2747
<MenuItem
2848
itemId="bulk-change-puppet-proxy-menu-item"
2949
key="bulk-change-puppet-proxy-menu-item"

0 commit comments

Comments
 (0)