Skip to content

Commit

Permalink
feat(KFLUXUI-175): give up SpaceBindingRequest and enjoy RoleBinding
Browse files Browse the repository at this point in the history
  • Loading branch information
testcara committed Jan 25, 2025
1 parent f576d82 commit cb18cea
Show file tree
Hide file tree
Showing 45 changed files with 1,189 additions and 769 deletions.
18 changes: 18 additions & 0 deletions src/__data__/role-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const defaultKonfluxRoleMap = {
'konflux-admin-user-actions': 'admin',
'konflux-contributor-user-actions': 'contributor',
'konflux-maintainer-user-actions': 'maintainer',
};

export const mockConfigMap = {
kind: 'ConfigMap',
apiVersion: 'v1',
metadata: {
name: 'konflux-public-info',
namespace: 'konflux-info',
},
data: {
'info.json':
'{\n "environment": "staging",\n "integrations": {\n "github": {\n "application_url": "https://github.com/apps/konflux-staging"\n },\n "sbom_server": {\n "url": "https://atlas.stage.devshift.net/sbom/content/\u003cPLACEHOLDER\u003e"\n },\n "image_controller": {\n "enabled": true,\n "notifications": [\n {\n "title": "SBOM-event-to-Bombino",\n "event": "repo_push",\n "method": "webhook",\n "config": {\n "url": "https://bombino.preprod.api.redhat.com/v1/sbom/quay/push"\n }\n }\n ]\n }\n },\n "rbac": [\n {\n "displayName": "admin",\n "description": "Full access to Konflux resources including secrets",\n "roleRef": {\n "apiGroup": "rbac.authorization.k8s.io",\n "kind": "ClusterRole",\n "name": "konflux-admin-user-actions"\n }\n },\n {\n "displayName": "maintainer",\n "description": "Partial access to Konflux resources without access to secrets",\n "roleRef": {\n "apiGroup": "rbac.authorization.k8s.io",\n "kind": "ClusterRole",\n "name": "konflux-maintainer-user-actions"\n }\n },\n {\n "displayName": "contributor",\n "description": "View access to Konflux resources without access to secrets",\n "roleRef": {\n "apiGroup": "rbac.authorization.k8s.io",\n "kind": "ClusterRole",\n "name": "konflux-contributor-user-actions"\n }\n }\n ]\n}\n',
},
};
77 changes: 77 additions & 0 deletions src/__data__/rolebinding-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { RoleBinding } from '../types';

export const mockRoleBinding: RoleBinding = {
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: { name: 'metadata-name', namespace: 'test-ws' },
subjects: [
{ apiGroup: 'rbac.authorization.k8s.io', name: 'user1', kind: 'User', namespace: 'test-ws' },
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'konflux-contributor-user-actions',
},
};

export const mockRoleBindings: RoleBinding[] = [
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: { name: 'metadata-name', namespace: 'test-ws' },
subjects: [
{ apiGroup: 'rbac.authorization.k8s.io', name: 'user1', kind: 'User', namespace: 'test-ws' },
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'konflux-contributor-user-actions',
},
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: { name: 'metadata-name-1', namespace: 'test-ws' },
subjects: [
{ apiGroup: 'rbac.authorization.k8s.io', name: 'user2', kind: 'User', namespace: 'test-ws' },
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'konflux-maintainer-user-actions',
},
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: { name: 'metadata-name-2', namespace: 'test-ws' },
subjects: [
{ apiGroup: 'rbac.authorization.k8s.io', name: 'user3', kind: 'User', namespace: 'test-ws' },
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'konflux-maintainer-user-actions',
},
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'RoleBinding',
metadata: { name: 'metadata-name-3', namespace: 'test-ws' },
subjects: [
{ apiGroup: 'rbac.authorization.k8s.io', name: 'user4', kind: 'User', namespace: 'test-ws' },
],
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'konflux-admin-user-actions',
},
},
];

export const mockRoleBindingWithoutUser: RoleBinding = {
...mockRoleBinding,
subjects: mockRoleBinding.subjects.map((subject) =>
subject.kind === 'User' ? { ...subject, kind: 'Group' } : subject,
),
};
23 changes: 23 additions & 0 deletions src/components/UserAccess/RBListHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const rbTableColumnClasses = {
username: 'pf-m-width-25',
role: 'pf-m-width-20',
status: 'pf-m-hidden pf-m-visible-on-md pf-m-width-20',
kebab: 'pf-v5-c-table__action',
};

export const RBListHeader = () => [
{
title: 'Username',
props: { className: rbTableColumnClasses.username },
},
{
title: 'Role',
props: { className: rbTableColumnClasses.role },
},
{
title: ' ',
props: {
className: rbTableColumnClasses.kebab,
},
},
];
33 changes: 33 additions & 0 deletions src/components/UserAccess/RBListRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { Bullseye, Spinner } from '@patternfly/react-core';
import { useRoleMap } from '../../hooks/useRole';
import { RowFunctionArgs, TableData } from '../../shared';
import ActionMenu from '../../shared/components/action-menu/ActionMenu';
import { RoleBinding } from '../../types';
import { rbTableColumnClasses } from './RBListHeader';
import { useRBActions } from './user-access-actions';

export const RBListRow: React.FC<React.PropsWithChildren<RowFunctionArgs<RoleBinding>>> = ({
obj,
}) => {
const actions = useRBActions(obj);
const { roleMap, roleMapLoading } = useRoleMap();

if (roleMapLoading) {
return (
<Bullseye>
<Spinner data-test="spinner" />
</Bullseye>
);
}

return (
<>
<TableData className={rbTableColumnClasses.username}>{obj.subjects[0].name}</TableData>
<TableData className={rbTableColumnClasses.role}>{roleMap[obj.roleRef.name]}</TableData>
<TableData className={rbTableColumnClasses.kebab}>
<ActionMenu actions={actions} />
</TableData>
</>
);
};
16 changes: 8 additions & 8 deletions src/components/UserAccess/RevokeAccessModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import {
ModalVariant,
} from '@patternfly/react-core';
import { K8sQueryDeleteResource } from '../../k8s';
import { SpaceBindingRequestModel } from '../../models';
import { WorkspaceBinding } from '../../types';
import { RoleBindingModel } from '../../models';
import { RoleBinding } from '../../types';
import { RawComponentProps } from '../modal/createModalLauncher';
import { invalidateWorkspaceQuery } from '../Workspace/utils';

type Props = RawComponentProps & {
sbr: WorkspaceBinding['bindingRequest'];
rb: RoleBinding;
username: string;
};

export const RevokeAccessModal: React.FC<React.PropsWithChildren<Props>> = ({
sbr,
rb,
username,
onClose,
modalProps,
Expand All @@ -38,10 +38,10 @@ export const RevokeAccessModal: React.FC<React.PropsWithChildren<Props>> = ({
setError(null);
try {
await K8sQueryDeleteResource({
model: SpaceBindingRequestModel,
model: RoleBindingModel,
queryOptions: {
name: sbr.name,
ns: sbr.namespace,
name: rb.metadata.name,
ns: rb.metadata.namespace,
},
});
await invalidateWorkspaceQuery();
Expand All @@ -50,7 +50,7 @@ export const RevokeAccessModal: React.FC<React.PropsWithChildren<Props>> = ({
setError((err as { message: string }).message || (err.toString() as string));
}
},
[onClose, sbr],
[onClose, rb],
);

return (
Expand Down
27 changes: 0 additions & 27 deletions src/components/UserAccess/SBRListHeader.tsx

This file was deleted.

26 changes: 0 additions & 26 deletions src/components/UserAccess/SBRListRow.tsx

This file was deleted.

27 changes: 0 additions & 27 deletions src/components/UserAccess/SBRStatusLabel.tsx

This file was deleted.

40 changes: 17 additions & 23 deletions src/components/UserAccess/UserAccessForm/EditAccessPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { useParams } from 'react-router-dom';
import { Bullseye, Spinner } from '@patternfly/react-core';
import { FULL_APPLICATION_TITLE } from '../../../consts/labels';
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
import { useSpaceBindingRequest } from '../../../hooks/useSpaceBindingRequests';
import { useRoleBindings } from '../../../hooks/useRoleBindings';
import { HttpError } from '../../../k8s/error';
import { SpaceBindingRequestModel } from '../../../models';
import { RoleBindingModel } from '../../../models';
import { RouterParams } from '../../../routes/utils';
import ErrorEmptyState from '../../../shared/components/empty-state/ErrorEmptyState';
import { AccessReviewResources } from '../../../types';
Expand All @@ -15,35 +15,30 @@ import { UserAccessFormPage } from './UserAccessFormPage';

const EditAccessPage: React.FC<React.PropsWithChildren<unknown>> = () => {
const { bindingName } = useParams<RouterParams>();
const { workspace, workspaceResource } = useWorkspaceInfo();
const binding = workspaceResource.status?.bindings?.find(
(b) => b.masterUserRecord === bindingName,
)?.bindingRequest;

const accessReviewResources: AccessReviewResources = binding
? [{ model: SpaceBindingRequestModel, verb: 'update' }]
: [{ model: SpaceBindingRequestModel, verb: 'create' }];
const { namespace } = useWorkspaceInfo();
const [roleBindings, loading, error] = useRoleBindings(namespace);
const binding = roleBindings.find((roleBinding) =>
roleBinding.subjects.some((subject) => subject.name === bindingName),
);

useDocumentTitle(`Edit access to workspace, ${workspace} | ${FULL_APPLICATION_TITLE}`);
useDocumentTitle(`Edit access to workspace, ${namespace} | ${FULL_APPLICATION_TITLE}`);

const [existingSBR, loaded, loadErr] = useSpaceBindingRequest(
binding.namespace,
workspace,
binding.name,
);
const accessReviewResources: AccessReviewResources = binding
? [{ model: RoleBindingModel, verb: 'update' }]
: [{ model: RoleBindingModel, verb: 'create' }];

if (binding && loadErr) {
const httpError = HttpError.fromCode((loadErr as { code: number }).code);
if (error) {
const httpError = HttpError.fromCode((error as { code: number }).code);
return (
<ErrorEmptyState
httpError={HttpError.fromCode((loadErr as { code: number }).code)}
title={`Unable to load space binding request ${binding.name}`}
httpError={HttpError.fromCode((error as { code: number }).code)}
title={`Unable to load role binding ${bindingName}`}
body={httpError.message}
/>
);
}

if (binding && !loaded) {
if (loading) {
return (
<Bullseye>
<Spinner data-test="spinner" />
Expand All @@ -53,9 +48,8 @@ const EditAccessPage: React.FC<React.PropsWithChildren<unknown>> = () => {

return (
<PageAccessCheck accessReviewResources={accessReviewResources}>
<UserAccessFormPage existingSbr={existingSBR} username={bindingName} edit />
<UserAccessFormPage existingRb={binding} username={bindingName} edit />
</PageAccessCheck>
);
};

export default EditAccessPage;
6 changes: 3 additions & 3 deletions src/components/UserAccess/UserAccessForm/PermissionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { WorkspaceRole } from '../../../types';
import { NamespaceRole } from '../../../types';

import './PermissionsTable.scss';

Expand All @@ -13,7 +13,7 @@ enum Permission {
}

// roles and permissions ADR: https://github.com/redhat-appstudio/book/blob/main/ADR/0011-roles-and-permissions.md
const permissions: Record<WorkspaceRole, Record<string, Permission[]>> = {
const permissions: Record<NamespaceRole, Record<string, Permission[]>> = {
contributor: {
Application: [Permission.Read],
Component: [Permission.Read],
Expand Down Expand Up @@ -75,7 +75,7 @@ const permissions: Record<WorkspaceRole, Record<string, Permission[]>> = {
},
};

export const PermissionsTable = React.memo<{ role: WorkspaceRole }>(({ role }) => {
export const PermissionsTable = React.memo<{ role: NamespaceRole }>(({ role }) => {
const rolePermissions = permissions[role];

return (
Expand Down
Loading

0 comments on commit cb18cea

Please sign in to comment.