From a10baadb17074cea1c385610139aa5439f967c18 Mon Sep 17 00:00:00 2001 From: Anna Vitova Date: Mon, 21 Aug 2023 15:00:56 +0200 Subject: [PATCH] feat(HMS-1245): UI AWS permission check --- src/API/index.js | 11 ++- src/API/queryKeys.js | 1 + .../steps/AccountCustomizations/aws.js | 72 ++++++++++++++++--- .../steps/AccountCustomizations/azure.js | 9 ++- .../steps/AccountCustomizations/gcp.js | 9 ++- src/Components/RegionsSelect/index.js | 4 +- src/Components/SourcesSelect/index.js | 16 +++-- 7 files changed, 104 insertions(+), 18 deletions(-) diff --git a/src/API/index.js b/src/API/index.js index f8f018d8..62fec197 100644 --- a/src/API/index.js +++ b/src/API/index.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { AZURE_PROVIDER } from '../constants'; +import { AWS_PROVIDER, AZURE_PROVIDER } from '../constants'; import { imageBuilderURL, provisioningUrl } from './helpers'; const typesUrlForProvider = (provider, region) => { @@ -72,3 +72,12 @@ export const fetchLaunchTemplates = async (sourceID, region) => { } = await axios.get(provisioningUrl(`sources/${sourceID}/launch_templates?region=${region}`)); return data; }; + +export const checkPermissions = async (provider, sourceID, region) => { + switch (provider) { + case AWS_PROVIDER: { + const { data } = await axios.get(provisioningUrl(`sources/${sourceID}/validate_permissions?region=${region}`)); + return data; + } + } +}; diff --git a/src/API/queryKeys.js b/src/API/queryKeys.js index 10267d52..1db666e1 100644 --- a/src/API/queryKeys.js +++ b/src/API/queryKeys.js @@ -4,3 +4,4 @@ export const PUBKEYS_QUERY_KEY = 'pubkeys'; export const instanceTypesQueryKeys = (region) => ['instanceTypes', region]; export const IMAGE_REGIONS_KEY = 'image_region'; export const TEMPLATES_KEY = 'templates'; +export const PERMISSION_CHECK_KEY = 'permissions'; diff --git a/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js b/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js index 8b89dfb4..5d7095a6 100644 --- a/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js +++ b/src/Components/ProvisioningWizard/steps/AccountCustomizations/aws.js @@ -1,7 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { Form, FormGroup, Popover, Title, Button } from '@patternfly/react-core'; +import { Form, FormGroup, Popover, Title, Button, FormAlert, Alert } from '@patternfly/react-core'; import { HelpIcon } from '@patternfly/react-icons'; +import { useQuery } from 'react-query'; import { AWS_PROVIDER } from '../../../../constants'; import { imageProps } from '../../helpers'; @@ -11,13 +12,23 @@ import InstanceTypesSelect from '../../../InstanceTypesSelect'; import RegionsSelect from '../../../RegionsSelect'; import { useWizardContext } from '../../../Common/WizardContext'; import TemplatesSelect from '../../../TemplateSelect'; +import { checkPermissions } from '../../../../API'; const AccountCustomizationsAWS = ({ setStepValidated, image }) => { const [{ chosenSource, chosenRegion, chosenInstanceType }, setWizardContext] = useWizardContext(); + const { data: missingPermissions } = useQuery( + [`permissions`, `${chosenRegion}-${chosenSource}`], + () => checkPermissions(image.provider, chosenSource, chosenRegion), + { + select: (perm) => perm.missing_entities, + enabled: !!chosenRegion && !!chosenSource, + } + ); const [validations, setValidation] = React.useState({ - sources: chosenSource ? 'success' : 'default', + sources: chosenSource ? ((missingPermissions || []).length == 0 ? 'success' : 'warning') : 'default', types: chosenInstanceType ? 'success' : 'default', amount: 'success', + region: 'default', }); const onRegionChange = ({ region, imageID }) => { @@ -34,18 +45,52 @@ const AccountCustomizationsAWS = ({ setStepValidated, image }) => { setStepValidated(!errorExists); }, [validations]); + React.useEffect(() => { + if ((missingPermissions || []).length != 0) { + if (validations.sources !== 'error') { + setValidation((prevValidations) => ({ + ...prevValidations, + sources: 'warning', + })); + } + if (validations.region !== 'error') { + setValidation((prevValidations) => ({ + ...prevValidations, + region: 'warning', + })); + } + } else { + setValidation((prevValidations) => ({ + ...prevValidations, + sources: chosenSource ? 'success' : 'default', + region: 'default', + })); + } + }, [missingPermissions]); + return (
Account and customizations | Amazon - + {(missingPermissions || []).length != 0 && ( + + + <> +

+ Check if policies in your {image.provider} account for the selected region are set + as{' '} + + our documentation + {' '} + recommends. Following permissions might be missing: +

+

{(missingPermissions || []).join(', ')}

+ +
+
+ )} + @@ -54,6 +99,7 @@ const AccountCustomizationsAWS = ({ setStepValidated, image }) => { sources: validation, })) } + validated={validations.sources} /> { } > - + { sources: validation, })) } + validated={validations.sources} /> { } > - + { sources: validation, })) } + validated={validations.sources} /> { } > - + { +const RegionsSelect = ({ provider, currentRegion, composeID, onChange, validated }) => { const [isOpen, setIsOpen] = React.useState(false); const { @@ -72,6 +72,7 @@ const RegionsSelect = ({ provider, currentRegion, composeID, onChange }) => { onToggle={onToggle} onSelect={onSelect} isDisabled={!MULTIPLE_REGION_SUPPORT.includes(provider)} + validated={validated} > {images.map(({ id, region }) => ( @@ -85,6 +86,7 @@ RegionsSelect.propTypes = { composeID: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, currentRegion: PropTypes.string, + validated: PropTypes.string.isRequired, }; export default RegionsSelect; diff --git a/src/Components/SourcesSelect/index.js b/src/Components/SourcesSelect/index.js index d769f61e..765361ad 100644 --- a/src/Components/SourcesSelect/index.js +++ b/src/Components/SourcesSelect/index.js @@ -6,7 +6,7 @@ import { imageProps } from '../ProvisioningWizard/helpers'; import { useSourcesForImage } from '../Common/Hooks/sources'; import { useWizardContext } from '../Common/WizardContext'; -const SourcesSelect = ({ setValidation, image }) => { +const SourcesSelect = ({ setValidation, image, validated }) => { const [{ chosenSource }, setWizardContext] = useWizardContext(); const [isOpen, setIsOpen] = React.useState(false); const [selected, setSelected] = React.useState(null); @@ -19,14 +19,18 @@ const SourcesSelect = ({ setValidation, image }) => { onSuccess: (sources) => { if (chosenSource) { setSelected(selectObject(chosenSource, sources.find((source) => source.id === chosenSource).name)); - setValidation('success'); + if (validated !== 'warning') { + setValidation('success'); + } } else if (sources.length === 1) { setSelected(selectObject(sources[0].id, sources[0].name)); setWizardContext((prevState) => ({ ...prevState, chosenSource: sources[0].id, })); - setValidation('success'); + if (validated !== 'warning') { + setValidation('success'); + } } }, }); @@ -42,7 +46,9 @@ const SourcesSelect = ({ setValidation, image }) => { ...prevState, chosenSource: selection.id, })); - setValidation('success'); + if (validated !== 'warning') { + setValidation('success'); + } } setIsOpen(false); }; @@ -76,6 +82,7 @@ const SourcesSelect = ({ setValidation, image }) => { onSelect={onSelect} placeholderText="Select account" aria-label="Select account" + validated={validated === 'error' || validated === 'warning' ? validated : 'default'} > {sources && selectItemsMapper()} @@ -85,6 +92,7 @@ const SourcesSelect = ({ setValidation, image }) => { SourcesSelect.propTypes = { setValidation: PropTypes.func.isRequired, image: imageProps, + validated: PropTypes.string.isRequired, }; export default SourcesSelect;