Skip to content

Commit

Permalink
WebShared: Update how request checkout handles kube resource related …
Browse files Browse the repository at this point in the history
…errors (#48168)

* WebShared: Update how request checkout handles kube resource related errors

* Fix bug where after create/cancel, specifiable fields were retained

* Remove single toggler for kube resources

* Address CR
  • Loading branch information
kimlisa authored Nov 1, 2024
1 parent a132be0 commit 3491836
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import { ActionMeta } from 'react-select';
import { Option } from 'shared/components/Select';
import { FieldSelectAsync } from 'shared/components/FieldSelect';

import { requiredField } from 'shared/components/Validation/rules';

import { CheckableOptionComponent } from '../CheckableOption';

import { PendingListItem, PendingKubeResourceItem } from './RequestCheckout';
Expand All @@ -36,19 +34,15 @@ export function KubeNamespaceSelector({
kubeClusterItem,
fetchKubeNamespaces,
savedResourceItems,
toggleResource,
bulkToggleKubeResources,
namespaceRequired,
}: {
kubeClusterItem: PendingListItem;
fetchKubeNamespaces(p: KubeNamespaceRequest): Promise<string[]>;
savedResourceItems: PendingListItem[];
toggleResource: (resource: PendingListItem) => void;
bulkToggleKubeResources: (
resources: PendingKubeResourceItem[],
resource: PendingListItem
) => void;
namespaceRequired: boolean;
}) {
// Flag is used to determine if we need to perform batch action
// eg: When menu is open, we want to apply changes only after
Expand Down Expand Up @@ -87,13 +81,18 @@ export function KubeNamespaceSelector({
);
return;
case 'remove-value':
toggleResource({
kind: 'namespace',
id: kubeClusterItem.id,
subResourceName: actionMeta.removedValue.value,
clusterName: kubeClusterItem.clusterName,
name: actionMeta.removedValue.value,
});
bulkToggleKubeResources(
[
{
kind: 'namespace',
id: kubeClusterItem.id,
subResourceName: actionMeta.removedValue.value,
clusterName: kubeClusterItem.clusterName,
name: actionMeta.removedValue.value,
},
],
kubeClusterItem
);
return;
}
}
Expand Down Expand Up @@ -144,7 +143,7 @@ export function KubeNamespaceSelector({
return (
<Box width="100%" mb={-3}>
<StyledSelect
label={`Namespaces${namespaceRequired ? ' (required)' : ''}:`}
label={`Namespaces`}
inputId={kubeClusterItem.id}
width="100%"
placeholder="Start typing a namespace and press enter"
Expand All @@ -162,11 +161,6 @@ export function KubeNamespaceSelector({
onChange={handleChange}
value={selectedOpts}
menuPosition="fixed" /* required to render dropdown out of its row */
rule={
namespaceRequired
? requiredField('namespace selection required')
: undefined
}
initOptionsOnMenuOpen={(opts: Option[]) => setInitOptions(opts)}
defaultOptions={initOptions}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,27 @@ export const FailedResourceRequest = () => (
</MemoryRouter>
);

export const FailedUnsupportedKubeResourceKind = () => (
export const FailedUnsupportedKubeResourceKindWithTooltip = () => (
<MemoryRouter>
<RequestCheckoutWithSlider
{...baseProps}
isResourceRequest={true}
fetchResourceRequestRolesAttempt={{
status: 'failed',
statusText:
'Your Teleport roles request_mode field restricts you from requesting kinds [kube_cluster] for Kubernetes cluster "pumpkin-kube-cluster". Allowed kinds: [pod secret]',
statusText: `your Teleport role's "request.kubernetes_resources" field did not allow requesting to some or all of the requested Kubernetes resources. allowed kinds for each requestable roles: test-role-1: [deployment], test-role-2: [pod secret configmap service serviceaccount kube_node persistentvolume persistentvolumeclaim deployment replicaset statefulset daemonset clusterrole kube_role clusterrolebinding rolebinding cronjob job certificatesigningrequest ingress]`,
}}
/>
</MemoryRouter>
);

export const FailedUnsupportedKubeResourceKindWithoutTooltip = () => (
<MemoryRouter>
<RequestCheckoutWithSlider
{...baseProps}
isResourceRequest={true}
fetchResourceRequestRolesAttempt={{
status: 'failed',
statusText: `your Teleport role's "request.kubernetes_resources" field did not allow requesting to some or all of the requested Kubernetes resources. allowed kinds for each requestable roles: test-role-1: [deployment]`,
}}
/>
</MemoryRouter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ import {
P3,
Subtitle2,
Text,
Mark,
} from 'design';
import { ArrowBack, ChevronDown, ChevronRight, Warning } from 'design/Icon';
import Table, { Cell } from 'design/DataTable';
import { Danger } from 'design/Alert';

import Validation, { useRule, Validator } from 'shared/components/Validation';
import { Attempt } from 'shared/hooks/useAttemptNext';
import { listToSentence, pluralize } from 'shared/utils/text';
import { pluralize } from 'shared/utils/text';
import { Option } from 'shared/components/Select';
import { FieldCheckbox } from 'shared/components/FieldCheckbox';
import { mergeRefs } from 'shared/libs/mergeRefs';
import { TextSelectCopyMulti } from 'shared/components/TextSelectCopy';
import { RequestableResourceKind } from 'shared/components/AccessRequests/NewRequest/resource';
import { HoverTooltip } from 'shared/components/ToolTip';

import { CreateRequest } from '../../Shared/types';
import { AssumeStartTime } from '../../AssumeStartTime/AssumeStartTime';
import { AccessDurationRequest } from '../../AccessDuration';
import {
checkForUnsupportedKubeRequestModes,
checkSupportForKubeResources,
isKubeClusterWithNamespaces,
type KubeNamespaceRequest,
} from '../kube';
Expand Down Expand Up @@ -191,15 +191,10 @@ export function RequestCheckout<T extends PendingListItem>({
});
}

const {
affectedKubeClusterName,
unsupportedKubeRequestModes,
requiresNamespaceSelect,
} = checkForUnsupportedKubeRequestModes(fetchResourceRequestRolesAttempt);

const hasUnsupportedKubeRequestModes = !!unsupportedKubeRequestModes;
const showRequestRoleErrBanner =
!hasUnsupportedKubeRequestModes && !requiresNamespaceSelect;
const { requestKubeResourceSupported, isRequestKubeResourceError } =
checkSupportForKubeResources(fetchResourceRequestRolesAttempt);
const hasUnsupporteKubeResourceKinds =
!requestKubeResourceSupported && isRequestKubeResourceError;

const isInvalidRoleSelection =
resourceRequestRoles.length > 0 &&
Expand All @@ -211,8 +206,7 @@ export function RequestCheckout<T extends PendingListItem>({
createAttempt.status === 'processing' ||
isInvalidRoleSelection ||
(fetchResourceRequestRolesAttempt.status === 'failed' &&
hasUnsupportedKubeRequestModes) ||
requiresNamespaceSelect ||
hasUnsupporteKubeResourceKinds) ||
fetchResourceRequestRolesAttempt.status === 'processing';

const cancelBtnDisabled =
Expand Down Expand Up @@ -269,13 +263,8 @@ export function RequestCheckout<T extends PendingListItem>({
<KubeNamespaceSelector
kubeClusterItem={item}
savedResourceItems={pendingAccessRequests}
toggleResource={toggleResource}
fetchKubeNamespaces={fetchKubeNamespaces}
bulkToggleKubeResources={bulkToggleKubeResources}
namespaceRequired={
requiresNamespaceSelect &&
affectedKubeClusterName.includes(item.id)
}
/>
</Flex>
</Flex>
Expand All @@ -288,21 +277,31 @@ export function RequestCheckout<T extends PendingListItem>({
<Validation>
{({ validator }) => (
<>
{showRequestRoleErrBanner &&
{!isRequestKubeResourceError &&
createAttempt.status !== 'failed' &&
fetchResourceRequestRolesAttempt.status === 'failed' && (
<Alert
kind="danger"
children={fetchResourceRequestRolesAttempt.statusText}
/>
)}
{hasUnsupportedKubeRequestModes && (
{hasUnsupporteKubeResourceKinds && (
<Alert kind="danger">
<HoverTooltip
position="left"
tipContent={
fetchResourceRequestRolesAttempt.statusText.length > 248
? fetchResourceRequestRolesAttempt.statusText
: null
}
>
<ShortenedText mb={2}>
{fetchResourceRequestRolesAttempt.statusText}
</ShortenedText>
</HoverTooltip>
<Text mb={2}>
You can only request Kubernetes resource{' '}
{pluralize(unsupportedKubeRequestModes.length, 'kind')}{' '}
<Mark>{listToSentence(unsupportedKubeRequestModes)}</Mark> for
cluster <Mark>{affectedKubeClusterName}</Mark>. Requesting those
resource kinds is currently only supported through the{' '}
The listed allowed kinds are currently only supported through
the{' '}
<ExternalLink
target="_blank"
href="https://goteleport.com/docs/connect-your-client/tsh/#installing-tsh"
Expand All @@ -316,14 +315,14 @@ export function RequestCheckout<T extends PendingListItem>({
>
tsh request search
</ExternalLink>{' '}
command that will help you construct the request.
that will help you construct the request.
</Text>
<Box width="360px">
<Box width="325px">
Example:
<TextSelectCopyMulti
lines={[
{
text: `tsh request search --kind=${unsupportedKubeRequestModes[0]} --kube-cluster=${affectedKubeClusterName} --all-kube-namespaces`,
text: `tsh request search --kind=ALLOWED_KIND --kube-cluster=CLUSTER_NAME --all-kube-namespaces`,
},
]}
/>
Expand Down Expand Up @@ -863,6 +862,12 @@ const StyledTable = styled(Table)`
overflow: hidden;
` as typeof Table;

const ShortenedText = styled(Text)`
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 6;
`;

export type RequestCheckoutWithSliderProps<
T extends PendingListItem = PendingListItem,
> = {
Expand Down
Loading

0 comments on commit 3491836

Please sign in to comment.