Skip to content

Commit

Permalink
Fixes #37395 - New Hosts page perform build actions
Browse files Browse the repository at this point in the history
Refs #37395 - Made the hosts controller handle both json and html
Refs #37395 - Fails correctly with url
Refs #37395 - Moved error notifications to a separate file
  • Loading branch information
parthaa committed Jun 4, 2024
1 parent c75c51b commit 29dc337
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 26 deletions.
74 changes: 74 additions & 0 deletions app/controllers/api/v2/hosts_bulk_actions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class HostsBulkActionsController < V2::BaseController
include Api::V2::BulkHostsExtension

before_action :find_deletable_hosts, :only => [:bulk_destroy]
before_action :find_editable_hosts, :only => [:build]

def_param_group :bulk_host_ids do
param :organization_id, :number, :required => true, :desc => N_("ID of the organization")
Expand All @@ -25,11 +26,84 @@ def bulk_destroy
process_response @hosts.destroy_all
end

api :PUT, "/hosts/bulk/build", N_("Build")
param_group :bulk_host_ids
param :reboot, :bool, N_("Reboot after build. Ignored if rebuild_configuration is passed.")
param :rebuild_configuration, :bool, N_("Rebuild configuration only")
def build
if Foreman::Cast.to_bool(params[:rebuild_configuration])
rebuild_config
else
reboot = Foreman::Cast.to_bool(params[:reboot])
manager = BulkHostsManager.new(hosts: @hosts)
missed_hosts = manager.build(reboot: reboot)
if missed_hosts.empty?
if reboot
process_response(true, { :message => n_("%s host set to build and rebooting.",
"%s hosts set to build and rebooting.",
@hosts.count) % @hosts.count,
})
else
process_response(true, { :message => n_("Built %s host",
"Built %s hosts", @hosts.count) % @hosts.count })
end
elsif reboot
render_error(:bulk_hosts_error, :status => :unprocessable_entity,
:locals => { :message => n_("Failed to build and reboot %s host",
"Failed to build and reboot %s hosts", missed_hosts.count) % missed_hosts.count,
:failed_host_ids => missed_hosts.map(&:id),
})
else
render_error(:bulk_hosts_error, :status => :unprocessable_entity,
:locals => { :message => n_("Failed to build %s host",
"Failed to build %s hosts", missed_hosts.count) % missed_hosts.count,
:failed_host_ids => missed_hosts.map(&:id),
})
end
end
end

protected

def action_permission
case params[:action]
when 'build'
'edit'
else
super
end
end

private

def find_deletable_hosts
find_bulk_hosts(:destroy_hosts, params)
end

def find_editable_hosts
find_bulk_hosts(:edit_hosts, params)
end

def rebuild_config
all_fails = BulkHostsManager.new(hosts: @hosts).rebuild_configuration
failed_host_ids = all_fails.flat_map { |_key, values| values&.map(&:id) }
failed_host_ids.compact!
failed_host_ids.uniq!

if failed_host_ids.empty?
process_response(true, { :message => n_("Rebuilt configuration for %s host",
"Rebuilt configuration for %s hosts",
@hosts.count) % @hosts.count })
else
render_error(:bulk_hosts_error, :status => :unprocessable_entity,
:locals => { :message => n_("Failed to rebuild configuration for %s host",
"Failed to rebuild configuration for %s hosts",
failed_host_ids.count) % failed_host_ids.count,
:failed_host_ids => failed_host_ids,
}
)
end
end
end
end
end
29 changes: 4 additions & 25 deletions app/controllers/hosts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -493,21 +493,13 @@ def rebuild_config
end

def submit_rebuild_config
all_fails = {}
@hosts.each do |host|
result = host.recreate_config
result.each_pair do |k, v|
all_fails[k] ||= []
all_fails[k] << host.name unless v
end
end

all_fails = BulkHostsManager.new(hosts: @hosts).rebuild_configuration
message = ''
all_fails.each_pair do |key, values|
unless values.empty?
message << ((n_("%{config_type} rebuild failed for host: %{host_names}.",
"%{config_type} rebuild failed for hosts: %{host_names}.",
values.count) % {:config_type => _(key), :host_names => values.to_sentence})) + " "
values.count) % {:config_type => _(key), :host_names => values.map(&:to_label).to_sentence})) + " "
end
end

Expand All @@ -521,21 +513,8 @@ def submit_rebuild_config

def submit_multiple_build
reboot = params[:host][:build] == '1' || false

missed_hosts = @hosts.select do |host|
success = true
forward_url_options(host)
begin
host.built(false) if host.build? && host.token_expired?
host.setBuild
host.power.reset if host.supports_power_and_running? && reboot
rescue => error
message = _('Failed to redeploy %s.') % host
Foreman::Logging.exception(message, error)
success = false
end
!success
end
@hosts.each { |host| forward_url_options(host) }
missed_hosts = BulkHostsManager.new(hosts: @hosts).build(reboot: reboot)

if missed_hosts.empty?
if reboot
Expand Down
1 change: 1 addition & 0 deletions app/registries/foreman/access_permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@
:"api/v2/hosts" => [:update, :disassociate, :forget_status],
:"api/v2/interfaces" => [:create, :update, :destroy],
:"api/v2/compute_resources" => [:associate],
:"api/v2/hosts_bulk_actions" => [:build],
}
map.permission :destroy_hosts, {:hosts => [:destroy, :multiple_actions, :reset_multiple, :multiple_destroy, :submit_multiple_destroy],
:"api/v2/hosts" => [:destroy],
Expand Down
34 changes: 34 additions & 0 deletions app/services/bulk_hosts_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class BulkHostsManager
def initialize(hosts:)
@hosts = hosts
end

def build(reboot: false)
# returns missed hosts
@hosts.select do |host|
success = true
begin
host.built(false) if host.build? && host.token_expired?
host.setBuild
host.power.reset if reboot && host.supports_power_and_running?
rescue => error
Foreman::Logging.exception("Failed to redeploy #{host}.", error)
success = false
end
!success
end
end

def rebuild_configuration
# returns a hash with a key/value configuration
all_fails = {}
@hosts.each do |host|
result = host.recreate_config
result.each_pair do |k, v|
all_fails[k] ||= []
all_fails[k] << host unless v
end
end
all_fails
end
end
7 changes: 7 additions & 0 deletions app/views/api/v2/errors/bulk_hosts_error.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node :message do
locals[:message]
end

node :failed_host_ids do
locals[:failed_host_ids]
end
1 change: 1 addition & 0 deletions config/routes/api/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# new v2 routes that point to v2
scope "(:apiv)", :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
match 'hosts/bulk', :to => 'hosts_bulk_actions#bulk_destroy', :via => [:delete]
match 'hosts/bulk/build', :to => 'hosts_bulk_actions#build', :via => [:put]

resources :architectures, :except => [:new, :edit] do
constraints(:id => /[^\/]+/) do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import {
Modal,
Button,
TextContent,
Text,
Checkbox,
Radio,
} from '@patternfly/react-core';
import { addToast } from '../../../ToastsList/slice';
import { translate as __ } from '../../../../common/I18n';
import { failedHostsToastParams } from '../helpers';
import { STATUS } from '../../../../constants';
import { selectAPIStatus } from '../../../../redux/API/APISelectors';
import { bulkBuildHosts, HOST_BUILD_KEY } from './actions';

const BulkBuildHostModal = ({
isOpen,
closeModal,
selectedCount,
orgId,
fetchBulkParams,
}) => {
const dispatch = useDispatch();
const [buildRadioChecked, setBuildRadioChecked] = useState(true);
const [rebootChecked, setRebootChecked] = useState(false);
const hostUpdateStatus = useSelector(state =>
selectAPIStatus(state, HOST_BUILD_KEY)
);
const handleModalClose = () => {
setRebootChecked(false);
setBuildRadioChecked(true);
closeModal();
};

const handleError = ({ response }) => {
handleModalClose();
dispatch(
addToast(
failedHostsToastParams({ ...response.data.error, key: HOST_BUILD_KEY })
)
);
};
const handleSave = () => {
const requestBody = {
included: {
search: fetchBulkParams(),
},
reboot: rebootChecked,
rebuild_configuration: !buildRadioChecked,
};

dispatch(bulkBuildHosts(requestBody, handleModalClose, handleError));
};

const handleBuildRadioSelected = selected => {
setBuildRadioChecked(selected);
if (!selected) {
setRebootChecked(false);
}
};
const modalActions = [
<Button
key="add"
ouiaId="bulk-build-hosts-modal-add-button"
variant="primary"
onClick={handleSave}
isDisabled={hostUpdateStatus === STATUS.PENDING}
isLoading={hostUpdateStatus === STATUS.PENDING}
>
{__('Confirm')}
</Button>,
<Button
key="cancel"
ouiaId="bulk-build-hosts-modal-cancel-button"
variant="link"
onClick={handleModalClose}
>
Cancel
</Button>,
];
return (
<Modal
isOpen={isOpen}
onClose={handleModalClose}
onEscapePress={handleModalClose}
title={__('Build management')}
width="50%"
position="top"
actions={modalActions}
id="bulk-build-hosts-modal"
key="bulk-build-hosts-modal"
ouiaId="bulk-build-hosts-modal"
>
<TextContent>
<Text ouiaId="bulk-set-build-options">
<FormattedMessage
defaultMessage={__(
'Choose an action that will be performed on {hosts}.'
)}
values={{
hosts: (
<strong>
<FormattedMessage
defaultMessage="{count, plural, one {# {singular}} other {# {plural}}}"
values={{
count: selectedCount,
singular: __('selected host'),
plural: __('selected hosts'),
}}
id="ccs-options-i18n"

Check warning on line 114 in webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js

View workflow job for this annotation

GitHub Actions / test (13, 2.7, 14)

You have a misspelled word: ccs on String

Check warning on line 114 in webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js

View workflow job for this annotation

GitHub Actions / test (13, 2.7, 18)

You have a misspelled word: ccs on String

Check warning on line 114 in webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js

View workflow job for this annotation

GitHub Actions / test (13, 3.0, 14)

You have a misspelled word: ccs on String

Check warning on line 114 in webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/buildHosts/BulkBuildHostModal.js

View workflow job for this annotation

GitHub Actions / test (13, 3.0, 18)

You have a misspelled word: ccs on String
/>
</strong>
),
}}
id="bulk-build-host-description"
/>
</Text>
</TextContent>
<hr />
<Radio
isChecked={buildRadioChecked}
name="buildHostRadioGroup"
onChange={checked => handleBuildRadioSelected(checked)}
label={__('Build')}
id="build-host-radio"
ouiaId="build-host-radio"
body={
<Checkbox
label={__('Reboot now')}
id="reboot-now-checkbox-id"
name="reboot-now"
isChecked={rebootChecked}
isDisabled={!buildRadioChecked}
onChange={setRebootChecked}
ouiaId="build-reboot-checkbox"
/>
}
/>
<hr />
<Radio
name="buildHostRadioGroup"
onChange={checked => handleBuildRadioSelected(!checked)}
label={__('Rebuild provisioning configuration only')}
id="rebuild-host-radio"
ouiaId="rebuild-host-radio"
/>
</Modal>
);
};

BulkBuildHostModal.propTypes = {
isOpen: PropTypes.bool,
closeModal: PropTypes.func,
selectedCount: PropTypes.number.isRequired,
fetchBulkParams: PropTypes.func.isRequired,
orgId: PropTypes.number.isRequired,
};

BulkBuildHostModal.defaultProps = {
isOpen: false,
closeModal: () => {},
};

export default BulkBuildHostModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { APIActions } from '../../../../redux/API';
import { foremanUrl } from '../../../../common/helpers';

export const HOST_BUILD_KEY = 'HOST_BUILD_KEY';
export const bulkBuildHosts = (params, handleSuccess, handleError) => {
const url = foremanUrl(`/api/v2/hosts/bulk/build`);
return APIActions.put({
key: HOST_BUILD_KEY,
url,
successToast: response => response.data.message,
handleSuccess,
handleError,
params,
});
};

export default bulkBuildHosts;
Loading

0 comments on commit 29dc337

Please sign in to comment.