oscap: add the compliance step to the wizard

This commit adds the Compliance step to the wizard. In this step the
user can select a policy from the list of available policies. Their
image is going to be updated with the necessary changes on the later
steps.

IB and The compliance endpoint are both returning the list of policies a
user has access to. The oscap step computes the intersection of the
policies accessible before showing the select list to the user.

HMS-2077
This commit is contained in:
Thomas Lavocat 2023-10-02 14:03:30 +02:00 committed by Klara Simickova
parent 1587ead4d9
commit 02a642df19
6 changed files with 209 additions and 1 deletions

View file

@ -19,6 +19,7 @@ import {
registration,
repositories,
review,
oscap,
} from './steps';
import {
fileSystemConfigurationValidator,
@ -95,6 +96,12 @@ const onSave = (values) => {
}
}
if (values['oscap-policy']) {
customizations.openscap = {
profile_id: values['oscap-policy'],
};
}
const requests = [];
if (values['target-environment']?.aws) {
const options =
@ -470,6 +477,10 @@ const requestToState = (composeRequest, distroInfo, isBeta, isProd) => {
formState['register-system'] = 'register-later';
}
// oscap policy
formState['oscap-policy'] =
composeRequest?.customizations?.openscap?.profile_id;
return formState;
} else {
return;
@ -495,6 +506,8 @@ const formStepHistory = (composeRequest, contentSourcesEnabled) => {
steps.push('registration');
}
steps.push('Compliance');
if (contentSourcesEnabled) {
steps.push('File system configuration', 'packages', 'repositories');
@ -654,6 +667,7 @@ const CreateImageWizard = () => {
fileSystemConfiguration,
imageName,
review,
oscap,
],
initialState: {
activeStep: initialStep || 'image-output', // name of the active step

View file

@ -16,6 +16,7 @@ import CentOSAcknowledgement from './formComponents/CentOSAcknowledgement';
import FileSystemConfiguration from './formComponents/FileSystemConfiguration';
import GalleryLayout from './formComponents/GalleryLayout';
import ImageOutputReleaseSelect from './formComponents/ImageOutputReleaseSelect';
import { Oscap } from './formComponents/Oscap';
import {
ContentSourcesPackages,
RedHatPackages,
@ -72,6 +73,7 @@ const ImageCreator = ({
'azure-sources-select': AzureSourcesSelect,
'azure-resource-groups': AzureResourceGroups,
'gallery-layout': GalleryLayout,
'oscap-profile-selector': Oscap,
registration: Registration,
...customComponentMapper,
}}

View file

@ -0,0 +1,155 @@
import React, { useState } from 'react';
import useFieldApi from '@data-driven-forms/react-form-renderer/use-field-api';
import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
import {
Alert,
Radio,
FormGroup,
Select,
SelectOption,
SelectVariant,
Spinner,
} from '@patternfly/react-core';
import PropTypes from 'prop-types';
import { useGetOscapProfilesQuery } from '../../../store/imageBuilderApi';
/**
* Component for the user to select the policy to apply to their image.
* The selected policy will be stored in the `oscap-policy` form state variable.
* The Component is shown or not depending on the ShowSelector variable.
*/
const PolicySelector = ({ input, showSelector }) => {
const { change, getState } = useFormApi();
const [policy, selectPolicy] = useState(getState()?.values?.['oscap-policy']);
const [isOpen, setIsOpen] = useState(false);
const { data, isFetching, isSuccess, isError, refetch } =
useGetOscapProfilesQuery(
{
distribution: getState()?.values?.['release'],
},
{
skip: !showSelector,
}
);
if (!showSelector) {
return undefined;
}
const handleToggle = () => {
if (!isOpen) {
refetch();
}
setIsOpen(!isOpen);
};
const handleClear = () => {
selectPolicy(undefined);
change(input.name, undefined);
};
const setPolicy = (_, selection) => {
selectPolicy(selection);
setIsOpen(false);
change(input.name, selection);
};
return (
<FormGroup
isRequired={true}
label={'Policy to use for this image'}
data-testid="policies-form-group"
>
<Select
ouiaId="policySelect"
variant={SelectVariant.typeahead}
onToggle={handleToggle}
onSelect={setPolicy}
onClear={handleClear}
selections={policy}
isOpen={isOpen}
placeholderText="Select a policy"
typeAheadAriaLabel="Select a policy"
isDisabled={!isSuccess}
>
{isSuccess &&
data.map((key, index) => <SelectOption key={index} value={key} />)}
{isFetching && (
<SelectOption isNoResultsOption={true} data-testid="policies-loading">
<Spinner isSVG size="md" />
</SelectOption>
)}
</Select>
{isError && (
<Alert
title="Error fetching the policies"
variant="danger"
isPlain
isInline
>
Cannot get the list of policies
</Alert>
)}
</FormGroup>
);
};
PolicySelector.propTypes = {
input: PropTypes.any,
showSelector: PropTypes.bool,
};
/**
* Component to prompt the use with two choices:
* - to add a policy, in which case the PolicySelector will allow the user to
* pick a policy to be stored in the `oscap-policy` variable.
* - to not add a policy, in which case the `oscap-policy` form state goes
* undefined.
*/
const AddPolicy = ({ input }) => {
const { change, getState } = useFormApi();
const oscapPolicy = getState()?.values?.['oscap-policy'];
const [wantsPolicy, setWantsPolicy] = useState(oscapPolicy !== undefined);
return (
<>
<FormGroup label="Compliance policy">
<Radio
name="add-a-policy"
className="pf-u-mt-md"
data-testid="add-a-policy-radio"
id="add-a-policy"
label="Add a policy"
isChecked={wantsPolicy}
onChange={() => {
setWantsPolicy(true);
}}
/>
<Radio
name="dont-add-a-policy"
className="pf-u-mt-md"
data-testid="dont-add-a-policy-radio"
id="dont-add-a-policy"
label="Do not add a policy"
isChecked={!wantsPolicy}
onChange={() => {
setWantsPolicy(false);
change(input.name, undefined);
}}
/>
</FormGroup>
<PolicySelector input={input} showSelector={wantsPolicy} />
</>
);
};
AddPolicy.propTypes = {
input: PropTypes.object,
};
export const Oscap = ({ ...props }) => {
const { input } = useFieldApi(props);
return <AddPolicy input={input} />;
};

View file

@ -1,6 +1,7 @@
export { default as awsTarget } from './aws';
export { default as googleCloudTarget } from './googleCloud';
export { default as msAzureTarget } from './msAzure';
export { default as oscap } from './oscap';
export { default as packages } from './packages';
export { default as packagesContentSources } from './packagesContentSources';
export { default as registration } from './registration';

View file

@ -0,0 +1,36 @@
import React from 'react';
import componentTypes from '@data-driven-forms/react-form-renderer/component-types';
import { Text } from '@patternfly/react-core';
import StepTemplate from './stepTemplate';
import CustomButtons from '../formComponents/CustomButtons';
const oscapStep = {
StepTemplate,
id: 'wizard-systemconfiguration-oscap',
title: 'OpenSCAP Compliance',
name: 'Compliance',
nextStep: 'File system configuration',
buttons: CustomButtons,
fields: [
{
component: componentTypes.PLAIN_TEXT,
name: 'oscap-text-component',
label: (
<Text>
Monitor regulatory compliance policies of registered RHEL systems you
must adhere to via OpenSCAP.
</Text>
),
},
{
component: 'oscap-profile-selector',
name: 'oscap-policy',
label: 'Available profiles for the distribution',
},
],
};
export default oscapStep;

View file

@ -69,7 +69,7 @@ const registrationStep = {
</Title>
),
name: 'registration',
nextStep: 'File system configuration',
nextStep: 'Compliance',
buttons: CustomButtons,
fields: [
{