Skip to content

Commit a3f4b6e

Browse files
authored
Add rolebinding to manage permissions-project tab (kubeflow#1273)
Signed-off-by: ppadti <[email protected]>
1 parent 3bf656a commit a3f4b6e

File tree

7 files changed

+120
-20
lines changed

7 files changed

+120
-20
lines changed

clients/ui/frontend/src/app/pages/settings/roleBinding/RoleBindingPermissions.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ type RoleBindingPermissionsProps = {
3232
}[];
3333
createRoleBinding: (roleBinding: RoleBindingKind) => Promise<RoleBindingKind>;
3434
deleteRoleBinding: (name: string, namespace: string) => Promise<K8sStatus>;
35-
3635
projectName: string;
3736
roleRefKind: RoleBindingRoleRef['kind'];
3837
roleRefName?: RoleBindingRoleRef['name'];

clients/ui/frontend/src/app/pages/settings/roleBinding/RoleBindingPermissionsNameInput.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { TextInput } from '@patternfly/react-core';
33
import { RoleBindingSubject, TypeaheadSelect } from 'mod-arch-shared';
4+
import { ProjectKind } from '~/app/shared/components/types';
45
import { RoleBindingPermissionsRBType } from './types';
6+
import { namespaceToProjectDisplayName } from './utils';
57

68
type RoleBindingPermissionsNameInputProps = {
79
subjectKind: RoleBindingSubject['kind'];
@@ -10,6 +12,7 @@ type RoleBindingPermissionsNameInputProps = {
1012
onClear: () => void;
1113
placeholderText: string;
1214
typeAhead?: string[];
15+
isProjectSubject?: boolean;
1316
};
1417

1518
const RoleBindingPermissionsNameInput: React.FC<RoleBindingPermissionsNameInputProps> = ({
@@ -19,8 +22,10 @@ const RoleBindingPermissionsNameInput: React.FC<RoleBindingPermissionsNameInputP
1922
onClear,
2023
placeholderText,
2124
typeAhead,
25+
isProjectSubject,
2226
}) => {
23-
// TODO: We don't have project context yet and need to add logic to show projects permission tab under manage permissions of MR - might need to move the project-context part to shared library
27+
// TODO: We don't have project context yet - might need to move the project-context part to shared library
28+
const projects: ProjectKind[] = [];
2429
if (!typeAhead) {
2530
return (
2631
<TextInput
@@ -30,15 +35,19 @@ const RoleBindingPermissionsNameInput: React.FC<RoleBindingPermissionsNameInputP
3035
type="text"
3136
value={value}
3237
placeholder={`Type ${
33-
subjectKind === RoleBindingPermissionsRBType.GROUP ? 'group name' : 'username'
38+
isProjectSubject
39+
? 'project name'
40+
: subjectKind === RoleBindingPermissionsRBType.GROUP
41+
? 'group name'
42+
: 'username'
3443
}`}
3544
onChange={(e, newValue) => onChange(newValue)}
3645
/>
3746
);
3847
}
3948

4049
const selectOptions = typeAhead.map((option) => {
41-
const displayName = option;
50+
const displayName = isProjectSubject ? namespaceToProjectDisplayName(option, projects) : option;
4251
return { value: displayName, content: displayName };
4352
});
4453
// If we've selected an option that doesn't exist via isCreatable, include it in the options so it remains selected

clients/ui/frontend/src/app/pages/settings/roleBinding/RoleBindingPermissionsTable.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type RoleBindingPermissionsTableProps = {
2020
roleRefKind: RoleBindingRoleRef['kind'];
2121
roleRefName?: RoleBindingRoleRef['name'];
2222
labels?: { [key: string]: string };
23+
isProjectSubject?: boolean;
2324
defaultRoleBindingName?: string;
2425
permissions: RoleBindingKind[];
2526
permissionOptions: {
@@ -46,6 +47,7 @@ const RoleBindingPermissionsTable: React.FC<RoleBindingPermissionsTableProps> =
4647
permissions,
4748
permissionOptions,
4849
typeAhead,
50+
isProjectSubject,
4951
isAdding,
5052
createRoleBinding,
5153
deleteRoleBinding,
@@ -109,6 +111,7 @@ const RoleBindingPermissionsTable: React.FC<RoleBindingPermissionsTableProps> =
109111
key="add-permissions-row"
110112
subjectKind={subjectKind}
111113
permissionOptions={permissionOptions}
114+
isProjectSubject={isProjectSubject}
112115
typeAhead={typeAhead}
113116
isEditing={false}
114117
isAdding
@@ -130,12 +133,15 @@ const RoleBindingPermissionsTable: React.FC<RoleBindingPermissionsTableProps> =
130133
}
131134
rowRenderer={(rb) => (
132135
<RoleBindingPermissionsTableRow
136+
isProjectSubject={isProjectSubject}
133137
defaultRoleBindingName={defaultRoleBindingName}
134138
key={rb.metadata.name || ''}
135139
permissionOptions={permissionOptions}
136140
roleBindingObject={rb}
137141
subjectKind={subjectKind}
138-
isEditing={firstSubject(rb) === '' || editCell.includes(rb.metadata.name)}
142+
isEditing={
143+
firstSubject(rb, isProjectSubject) === '' || editCell.includes(rb.metadata.name)
144+
}
139145
isAdding={false}
140146
typeAhead={typeAhead}
141147
onChange={(subjectName, rbRoleRefName) => {

clients/ui/frontend/src/app/pages/settings/roleBinding/RoleBindingPermissionsTableRow.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import {
2424
RoleBindingSubject,
2525
} from 'mod-arch-shared';
2626
import useUser from '~/app/hooks/useUser';
27+
import { ProjectKind } from '~/app/shared/components/types';
2728
import {
2829
castRoleBindingPermissionsRoleType,
2930
firstSubject,
3031
roleLabel,
3132
isCurrentUserChanging,
33+
projectDisplayNameToNamespace,
3234
} from './utils';
3335
import { RoleBindingPermissionsRoleType } from './types';
3436
import RoleBindingPermissionsNameInput from './RoleBindingPermissionsNameInput';
@@ -46,13 +48,18 @@ type RoleBindingPermissionsTableRowProps = {
4648
description: string;
4749
}[];
4850
typeAhead?: string[];
51+
isProjectSubject?: boolean;
4952
onChange: (name: string, roleType: RoleBindingPermissionsRoleType) => void;
5053
onCancel: () => void;
5154
onEdit?: () => void;
5255
onDelete?: () => void;
5356
};
5457

55-
const defaultValueName = (obj: RoleBindingKind) => firstSubject(obj);
58+
const defaultValueName = (
59+
obj: RoleBindingKind,
60+
isProjectSubject?: boolean,
61+
projects?: ProjectKind[],
62+
) => firstSubject(obj, isProjectSubject, projects);
5663
const defaultValueRole = (obj: RoleBindingKind) =>
5764
castRoleBindingPermissionsRoleType(obj.roleRef.name);
5865

@@ -64,19 +71,21 @@ const RoleBindingPermissionsTableRow: React.FC<RoleBindingPermissionsTableRowPro
6471
defaultRoleBindingName,
6572
permissionOptions,
6673
typeAhead,
74+
isProjectSubject,
6775
onChange,
6876
onCancel,
6977
onEdit,
7078
onDelete,
7179
}) => {
72-
// TODO: We don't have project context yet and need to add logic to show projects permission tab under manage permissions of MR - might need to move the project-context part to shared library
80+
// TODO: We don't have project context yet - might need to move the project-context part to shared library
81+
const projects: ProjectKind[] = React.useMemo(() => [], []);
7382
const currentUser = useUser();
7483
const isCurrentUserBeingChanged = isCurrentUserChanging(obj, currentUser.userId);
7584
const [roleBindingName, setRoleBindingName] = React.useState(() => {
7685
if (isAdding || !obj) {
7786
return '';
7887
}
79-
return defaultValueName(obj);
88+
return defaultValueName(obj, isProjectSubject, projects);
8089
});
8190
const [roleBindingRoleRef, setRoleBindingRoleRef] =
8291
React.useState<RoleBindingPermissionsRoleType>(() => {
@@ -94,10 +103,14 @@ const RoleBindingPermissionsTableRow: React.FC<RoleBindingPermissionsTableRowPro
94103
//Sync local state with props if exiting edit mode
95104
React.useEffect(() => {
96105
if (!isEditing && obj) {
97-
setRoleBindingName(defaultValueName(obj));
106+
setRoleBindingName(
107+
isProjectSubject
108+
? defaultValueName(obj, isProjectSubject, projects)
109+
: defaultValueName(obj),
110+
);
98111
setRoleBindingRoleRef(defaultValueRole(obj));
99112
}
100-
}, [obj, isEditing]);
113+
}, [obj, isEditing, isProjectSubject, projects]);
101114

102115
return (
103116
<>
@@ -112,8 +125,9 @@ const RoleBindingPermissionsTableRow: React.FC<RoleBindingPermissionsTableRowPro
112125
setRoleBindingName(selection);
113126
}}
114127
onClear={() => setRoleBindingName('')}
115-
placeholderText="Select a group"
128+
placeholderText={isProjectSubject ? 'Select or enter a project' : 'Select a group'}
116129
typeAhead={typeAhead}
130+
isProjectSubject={isProjectSubject}
117131
/>
118132
) : (
119133
<Content component="p">
@@ -179,7 +193,15 @@ const RoleBindingPermissionsTableRow: React.FC<RoleBindingPermissionsTableRowPro
179193
setShowModal(true);
180194
} else {
181195
setIsLoading(true);
182-
onChange(roleBindingName, roleBindingRoleRef);
196+
onChange(
197+
isProjectSubject
198+
? `system:serviceaccounts:${projectDisplayNameToNamespace(
199+
roleBindingName,
200+
projects,
201+
)}`
202+
: roleBindingName,
203+
roleBindingRoleRef,
204+
);
183205
setIsLoading(false);
184206
}
185207
}}
@@ -245,7 +267,15 @@ const RoleBindingPermissionsTableRow: React.FC<RoleBindingPermissionsTableRowPro
245267
}}
246268
onEdit={() => {
247269
setIsLoading(true);
248-
onChange(roleBindingName, roleBindingRoleRef);
270+
onChange(
271+
isProjectSubject
272+
? `system:serviceaccounts:${projectDisplayNameToNamespace(
273+
roleBindingName,
274+
projects,
275+
)}`
276+
: roleBindingName,
277+
roleBindingRoleRef,
278+
);
249279
setIsLoading(false);
250280
setShowModal(false);
251281
}}

clients/ui/frontend/src/app/pages/settings/roleBinding/RoleBindingPermissionsTableSection.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type RoleBindingPermissionsTableSectionAltProps = {
4040
typeModifier: string;
4141
defaultRoleBindingName?: string;
4242
labels?: { [key: string]: string };
43+
isProjectSubject?: boolean;
4344
};
4445

4546
const RoleBindingPermissionsTableSection: React.FC<RoleBindingPermissionsTableSectionAltProps> = ({
@@ -57,6 +58,7 @@ const RoleBindingPermissionsTableSection: React.FC<RoleBindingPermissionsTableSe
5758
typeModifier,
5859
defaultRoleBindingName,
5960
labels,
61+
isProjectSubject,
6062
}) => {
6163
const [addField, setAddField] = React.useState(false);
6264
const [error, setError] = React.useState<React.ReactNode>();
@@ -72,14 +74,20 @@ const RoleBindingPermissionsTableSection: React.FC<RoleBindingPermissionsTableSe
7274
>
7375
<HeaderIcon
7476
type={
75-
subjectKind === RoleBindingPermissionsRBType.USER
76-
? ProjectObjectType.user
77-
: ProjectObjectType.group
77+
isProjectSubject
78+
? ProjectObjectType.project
79+
: subjectKind === RoleBindingPermissionsRBType.USER
80+
? ProjectObjectType.user
81+
: ProjectObjectType.group
7882
}
7983
/>
8084
<FlexItem>
8185
<Title id={`user-permission-${typeModifier}`} headingLevel="h2" size="xl">
82-
{subjectKind === RoleBindingPermissionsRBType.USER ? 'Users' : 'Groups'}
86+
{isProjectSubject
87+
? 'Projects'
88+
: subjectKind === RoleBindingPermissionsRBType.USER
89+
? 'Users'
90+
: 'Groups'}
8391
</Title>
8492
</FlexItem>
8593
</Flex>
@@ -93,6 +101,7 @@ const RoleBindingPermissionsTableSection: React.FC<RoleBindingPermissionsTableSe
93101
namespace={projectName}
94102
roleRefKind={roleRefKind}
95103
roleRefName={roleRefName}
104+
isProjectSubject={isProjectSubject}
96105
labels={labels}
97106
subjectKind={subjectKind}
98107
typeAhead={typeAhead}
@@ -133,7 +142,11 @@ const RoleBindingPermissionsTableSection: React.FC<RoleBindingPermissionsTableSe
133142
onClick={() => setAddField(true)}
134143
style={{ paddingLeft: 'var(--pf-t--global--spacer--lg)' }}
135144
>
136-
{subjectKind === RoleBindingPermissionsRBType.USER ? 'Add user' : 'Add group'}
145+
{isProjectSubject
146+
? 'Add project'
147+
: subjectKind === RoleBindingPermissionsRBType.USER
148+
? 'Add user'
149+
: 'Add group'}
137150
</Button>
138151
</StackItem>
139152
</Stack>

clients/ui/frontend/src/app/pages/settings/roleBinding/utils.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { capitalize } from '@patternfly/react-core';
22
import { RoleBindingKind } from 'mod-arch-shared';
33
import { patchRoleBinding } from '~/app/api/k8s';
4+
import { getDisplayNameFromK8sResource } from '~/app/shared/components/utils';
5+
import { ProjectKind } from '~/app/shared/components/types';
46
import { RoleBindingPermissionsRBType, RoleBindingPermissionsRoleType } from './types';
57

68
export const filterRoleBindingSubjects = (
@@ -28,8 +30,17 @@ export const castRoleBindingPermissionsRoleType = (
2830
return RoleBindingPermissionsRoleType.CUSTOM;
2931
};
3032

31-
export const firstSubject = (roleBinding: RoleBindingKind): string =>
32-
roleBinding.subjects[0]?.name || '';
33+
export const firstSubject = (
34+
roleBinding: RoleBindingKind,
35+
isProjectSubject?: boolean,
36+
project?: ProjectKind[],
37+
): string =>
38+
(isProjectSubject && project
39+
? namespaceToProjectDisplayName(
40+
roleBinding.subjects[0]?.name.replace(/^system:serviceaccounts:/, ''),
41+
project,
42+
)
43+
: roleBinding.subjects[0]?.name) || '';
3344

3445
export const roleLabel = (value: RoleBindingPermissionsRoleType): string => {
3546
if (value === RoleBindingPermissionsRoleType.EDIT) {
@@ -77,3 +88,21 @@ export const tryPatchRoleBinding = async (
7788
return false;
7889
}
7990
};
91+
92+
export const namespaceToProjectDisplayName = (
93+
namespace: string,
94+
projects: ProjectKind[],
95+
): string => {
96+
const project = projects.find((p) => p.metadata.name === namespace);
97+
return project ? getDisplayNameFromK8sResource(project) : namespace;
98+
};
99+
100+
export const projectDisplayNameToNamespace = (
101+
displayName: string,
102+
projects: ProjectKind[],
103+
): string => {
104+
const project = projects.find(
105+
(p) => p.metadata.annotations?.['openshift.io/display-name'] === displayName,
106+
);
107+
return project?.metadata.name || displayName;
108+
};

clients/ui/frontend/src/app/shared/components/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,17 @@ export type K8sDSGResource = K8sResourceCommon & {
3434
name: string;
3535
};
3636
};
37+
38+
export type ProjectKind = K8sResourceCommon & {
39+
metadata: {
40+
annotations?: DisplayNameAnnotations &
41+
Partial<{
42+
'openshift.io/requester': string; // the username of the user that requested this project
43+
}>;
44+
labels?: Record<string, string>;
45+
name: string;
46+
};
47+
status?: {
48+
phase: 'Active' | 'Terminating';
49+
};
50+
};

0 commit comments

Comments
 (0)