From b5da0102703352c4a9721bda34b70de9c26066f3 Mon Sep 17 00:00:00 2001 From: Dallas <5322142+gitdallas@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:53:24 -0500 Subject: [PATCH] feat(13045): mr permissions and rolebindings (#3249) Signed-off-by: gitdallas <5322142+gitdallas@users.noreply.github.com> --- .../api/modelRegistryRoleBindings/index.ts | 73 +++++++++++++++ .../modelRegistryRolebindingsUtils.ts | 54 +++++++++++ backend/src/types.ts | 1 + .../cypress/cypress/support/commands/odh.ts | 13 +++ .../modelRegistryPermissions.cy.ts | 90 +++++++------------ .../roleBinding/RoleBindingPermissions.tsx | 10 ++- .../RoleBindingPermissionsTable.tsx | 8 +- .../RoleBindingPermissionsTableSection.tsx | 8 +- .../ModelRegistriesPermissions.tsx | 6 ++ .../ProjectsTab/ProjectsSettingsTab.tsx | 6 ++ .../useModelRegistryRoleBindings.ts | 14 +-- .../projectSharing/ProjectSharing.tsx | 3 + .../services/modelRegistrySettingsService.ts | 28 +++++- 13 files changed, 242 insertions(+), 72 deletions(-) create mode 100644 backend/src/routes/api/modelRegistryRoleBindings/index.ts create mode 100644 backend/src/routes/api/modelRegistryRoleBindings/modelRegistryRolebindingsUtils.ts diff --git a/backend/src/routes/api/modelRegistryRoleBindings/index.ts b/backend/src/routes/api/modelRegistryRoleBindings/index.ts new file mode 100644 index 0000000000..b352485b87 --- /dev/null +++ b/backend/src/routes/api/modelRegistryRoleBindings/index.ts @@ -0,0 +1,73 @@ +import { FastifyReply, FastifyRequest } from 'fastify'; +import { secureAdminRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; +import { + createModelRegistryRoleBinding, + deleteModelRegistriesRolebinding, + listModelRegistryRoleBindings, +} from './modelRegistryRolebindingsUtils'; +import { V1RoleBinding } from '@kubernetes/client-node'; +import { getModelRegistryNamespace } from '../modelRegistries/modelRegistryUtils'; + +export default async (fastify: KubeFastifyInstance): Promise => { + fastify.get( + `/`, + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { + try { + const mrNamespace = getModelRegistryNamespace(fastify); + return listModelRegistryRoleBindings(fastify, mrNamespace); + } catch (e) { + fastify.log.error( + `ModelRegistry RoleBindings could not be listed, ${ + e.response?.body?.message || e.message + }`, + ); + reply.send(e); + } + }), + ); + + fastify.post( + '/', + secureAdminRoute(fastify)( + async (request: FastifyRequest<{ Body: V1RoleBinding }>, reply: FastifyReply) => { + const rbRequest = request.body; + try { + const mrNamespace = getModelRegistryNamespace(fastify); + return createModelRegistryRoleBinding(fastify, rbRequest, mrNamespace); + } catch (e) { + if (e.response?.statusCode === 409) { + fastify.log.warn(`Rolebinding already present, skipping creation.`); + return {}; + } + + fastify.log.error( + `rolebinding could not be created: ${e.response?.body?.message || e.message}`, + ); + reply.send(new Error(e.response?.body?.message)); + } + }, + ), + ); + + fastify.delete( + '/:name', + secureAdminRoute(fastify)( + async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) => { + const modelRegistryNamespace = await getModelRegistryNamespace(fastify); + const { name } = request.params; + try { + const mrNamespace = getModelRegistryNamespace(fastify); + return deleteModelRegistriesRolebinding(fastify, name, mrNamespace); + } catch (e) { + fastify.log.error( + `RoleBinding ${name} could not be deleted from ${modelRegistryNamespace}, ${ + e.response?.body?.message || e.message + }`, + ); + reply.send(e); + } + }, + ), + ); +}; diff --git a/backend/src/routes/api/modelRegistryRoleBindings/modelRegistryRolebindingsUtils.ts b/backend/src/routes/api/modelRegistryRoleBindings/modelRegistryRolebindingsUtils.ts new file mode 100644 index 0000000000..77a3808868 --- /dev/null +++ b/backend/src/routes/api/modelRegistryRoleBindings/modelRegistryRolebindingsUtils.ts @@ -0,0 +1,54 @@ +import { K8sStatus, KnownLabels, KubeFastifyInstance } from '../../../types'; +import { V1RoleBinding } from '@kubernetes/client-node'; + +const MODEL_REGISTRY_ROLE_BINDING_API_GROUP = 'rbac.authorization.k8s.io'; +const MODEL_REGISTRY_ROLE_BINDING_API_VERSION = 'v1'; +const MODEL_REGISTRY_ROLE_BINDING_PLURAL = 'rolebindings'; + +export const listModelRegistryRoleBindings = async ( + fastify: KubeFastifyInstance, + mrNamespace: string, +): Promise<{ items: V1RoleBinding[] }> => { + const response = await (fastify.kube.customObjectsApi.listNamespacedCustomObject( + MODEL_REGISTRY_ROLE_BINDING_API_GROUP, + MODEL_REGISTRY_ROLE_BINDING_API_VERSION, + mrNamespace, + MODEL_REGISTRY_ROLE_BINDING_PLURAL, + undefined, + undefined, + undefined, + KnownLabels.LABEL_SELECTOR_MODEL_REGISTRY, + ) as Promise<{ body: { items: V1RoleBinding[] } }>); + return response.body; +}; + +export const createModelRegistryRoleBinding = async ( + fastify: KubeFastifyInstance, + rbRequest: V1RoleBinding, + mrNamespace: string, +): Promise => { + const response = await (fastify.kube.customObjectsApi.createNamespacedCustomObject( + MODEL_REGISTRY_ROLE_BINDING_API_GROUP, + MODEL_REGISTRY_ROLE_BINDING_API_VERSION, + mrNamespace, + MODEL_REGISTRY_ROLE_BINDING_PLURAL, + rbRequest, + ) as Promise<{ body: V1RoleBinding }>); + return response.body; +}; + +export const deleteModelRegistriesRolebinding = async ( + fastify: KubeFastifyInstance, + roleBindingName: string, + mrNamespace: string, +): Promise => { + const response = await (fastify.kube.customObjectsApi.deleteNamespacedCustomObject( + MODEL_REGISTRY_ROLE_BINDING_API_GROUP, + MODEL_REGISTRY_ROLE_BINDING_API_VERSION, + mrNamespace, + MODEL_REGISTRY_ROLE_BINDING_PLURAL, + roleBindingName, + ) as Promise<{ body: K8sStatus }>); + + return response.body; +}; diff --git a/backend/src/types.ts b/backend/src/types.ts index 90ed0f93fb..2c0d6ecc22 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -991,6 +991,7 @@ export enum KnownLabels { MODEL_SERVING_PROJECT = 'modelmesh-enabled', DATA_CONNECTION_AWS = 'opendatahub.io/managed', CONNECTION_TYPE = 'opendatahub.io/connection-type', + LABEL_SELECTOR_MODEL_REGISTRY = 'component=model-registry', } type ComponentNames = diff --git a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts index dc854147b2..10a9918dcf 100644 --- a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts +++ b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts @@ -629,6 +629,19 @@ declare global { path: { name: string }; }, response: SuccessErrorResponse, + ) => Cypress.Chainable) & + (( + type: 'GET /api/modelRegistryRoleBindings', + response: OdhResponse>, + ) => Cypress.Chainable) & + (( + type: 'DELETE /api/modelRegistryRoleBindings/:name', + options: { path: { name: string } }, + response: OdhResponse, + ) => Cypress.Chainable) & + (( + type: 'POST /api/modelRegistryRoleBindings', + response: OdhResponse, ) => Cypress.Chainable); } } diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistryPermissions.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistryPermissions.cy.ts index ad5dbf699a..7214d73710 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistryPermissions.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistryPermissions.cy.ts @@ -6,7 +6,6 @@ import { GroupModel, ModelRegistryModel, ProjectModel, - RoleBindingModel, } from '~/__tests__/cypress/cypress/utils/models'; import type { RoleBindingSubject } from '~/k8sTypes'; import { asProductAdminUser, asProjectEditUser } from '~/__tests__/cypress/cypress/utils/mockUsers'; @@ -69,8 +68,8 @@ const initIntercepts = ({ isEmpty = false, hasPermission = true }: HandlersProps mockProjectK8sResource({ k8sName: 'project-name', displayName: 'Project' }), ]), ); - cy.interceptK8sList( - { model: RoleBindingModel, ns: MODEL_REGISTRY_DEFAULT_NAMESPACE }, + cy.interceptOdh( + 'GET /api/modelRegistryRoleBindings', mockK8sResourceList( isEmpty ? [] @@ -155,9 +154,8 @@ describe('MR Permissions', () => { it('Add user', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'new-example-mr-user', @@ -199,9 +197,8 @@ describe('MR Permissions', () => { it('Edit user', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'edited-user', @@ -210,9 +207,9 @@ describe('MR Permissions', () => { modelRegistryName: 'example-mr', }), ).as('editUser'); - cy.interceptK8s( - 'DELETE', - { model: RoleBindingModel, ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'example-mr-user' }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'example-mr-user' } }, mock200Status({}), ).as('deleteUser'); @@ -248,11 +245,12 @@ describe('MR Permissions', () => { it('Delete user', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'DELETE', - { model: RoleBindingModel, ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'example-mr-user' }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'example-mr-user' } }, mock200Status({}), ).as('deleteUser'); + modelRegistryPermissions.visit('example-mr'); userTable.getTableRow('example-mr-user').findKebabAction('Delete').click(); @@ -280,9 +278,8 @@ describe('MR Permissions', () => { it('Add group', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'new-example-mr-group', @@ -329,9 +326,8 @@ describe('MR Permissions', () => { it('Edit group', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, name: 'example-mr-group-option', @@ -340,13 +336,9 @@ describe('MR Permissions', () => { modelRegistryName: 'example-mr', }), ).as('editGroup'); - cy.interceptK8s( - 'DELETE', - { - model: RoleBindingModel, - ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, - name: 'example-mr-users-2', - }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'example-mr-users-2' } }, mock200Status({}), ).as('deleteGroup'); @@ -389,13 +381,9 @@ describe('MR Permissions', () => { it('Delete group', () => { initIntercepts({ isEmpty: false }); - cy.interceptK8s( - 'DELETE', - { - model: RoleBindingModel, - ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, - name: 'example-mr-users-2', - }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'example-mr-users-2' } }, mock200Status({}), ).as('deleteGroup'); @@ -439,9 +427,8 @@ describe('MR Permissions', () => { }); it('Add project', () => { - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, subjects: projectSubjects, @@ -476,9 +463,8 @@ describe('MR Permissions', () => { }); it('Edit project', () => { - cy.interceptK8s( - 'POST', - RoleBindingModel, + cy.interceptOdh( + 'POST /api/modelRegistryRoleBindings', mockRoleBindingK8sResource({ namespace: MODEL_REGISTRY_DEFAULT_NAMESPACE, subjects: projectSubjects, @@ -486,13 +472,9 @@ describe('MR Permissions', () => { modelRegistryName: 'example-mr', }), ).as('editProject'); - cy.interceptK8s( - 'DELETE', - { - model: RoleBindingModel, - ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, - name: 'test-name-view', - }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'test-name-view' } }, mock200Status({}), ).as('deleteProject'); @@ -523,18 +505,12 @@ describe('MR Permissions', () => { }); it('Delete project', () => { - cy.interceptK8s( - 'DELETE', - { - model: RoleBindingModel, - ns: MODEL_REGISTRY_DEFAULT_NAMESPACE, - name: 'test-name-view', - }, + cy.interceptOdh( + 'DELETE /api/modelRegistryRoleBindings/:name', + { path: { name: 'test-name-view' } }, mock200Status({}), ).as('deleteProject'); - projectTable.getTableRow('Test Project').findKebabAction('Delete').click(); - cy.wait('@deleteProject'); }); }); diff --git a/frontend/src/concepts/roleBinding/RoleBindingPermissions.tsx b/frontend/src/concepts/roleBinding/RoleBindingPermissions.tsx index 105f7d98d1..55523561b5 100644 --- a/frontend/src/concepts/roleBinding/RoleBindingPermissions.tsx +++ b/frontend/src/concepts/roleBinding/RoleBindingPermissions.tsx @@ -11,7 +11,7 @@ import { EmptyStateHeader, } from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; -import { K8sResourceCommon } from '@openshift/dynamic-plugin-sdk-utils'; +import { K8sResourceCommon, K8sStatus } from '@openshift/dynamic-plugin-sdk-utils'; import { GroupKind, RoleBindingKind, RoleBindingRoleRef } from '~/k8sTypes'; import { ProjectSectionID } from '~/pages/projects/screens/detail/types'; import { ContextResourceData } from '~/types'; @@ -27,6 +27,8 @@ type RoleBindingPermissionsProps = { type: RoleBindingPermissionsRoleType; description: string; }[]; + createRoleBinding: (roleBinding: RoleBindingKind) => Promise; + deleteRoleBinding: (name: string, namespace: string) => Promise; projectName: string; roleRefKind: RoleBindingRoleRef['kind']; roleRefName?: RoleBindingRoleRef['name']; @@ -42,6 +44,8 @@ const RoleBindingPermissions: React.FC = ({ defaultRoleBindingName, permissionOptions, projectName, + createRoleBinding, + deleteRoleBinding, roleRefKind, roleRefName, labels, @@ -98,6 +102,8 @@ const RoleBindingPermissions: React.FC = ({ subjectKind={RoleBindingPermissionsRBType.USER} refresh={refreshRB} typeModifier="user" + createRoleBinding={createRoleBinding} + deleteRoleBinding={deleteRoleBinding} /> ); @@ -117,6 +123,8 @@ const RoleBindingPermissions: React.FC = ({ groups.length > 0 ? groups.map((group: GroupKind) => group.metadata.name) : undefined } typeModifier="group" + createRoleBinding={createRoleBinding} + deleteRoleBinding={deleteRoleBinding} /> ); diff --git a/frontend/src/concepts/roleBinding/RoleBindingPermissionsTable.tsx b/frontend/src/concepts/roleBinding/RoleBindingPermissionsTable.tsx index 2e58c9b8be..8e3f34c016 100644 --- a/frontend/src/concepts/roleBinding/RoleBindingPermissionsTable.tsx +++ b/frontend/src/concepts/roleBinding/RoleBindingPermissionsTable.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import { K8sResourceCommon } from '@openshift/dynamic-plugin-sdk-utils'; +import { K8sResourceCommon, K8sStatus } from '@openshift/dynamic-plugin-sdk-utils'; import { Table } from '~/components/table'; import { RoleBindingKind, RoleBindingRoleRef, RoleBindingSubject } from '~/k8sTypes'; -import { deleteRoleBinding, generateRoleBindingPermissions, createRoleBinding } from '~/api'; +import { generateRoleBindingPermissions } from '~/api'; import RoleBindingPermissionsTableRow from './RoleBindingPermissionsTableRow'; import { columnsRoleBindingPermissions } from './data'; import { RoleBindingPermissionsRoleType } from './types'; @@ -25,6 +25,8 @@ type RoleBindingPermissionsTableProps = { }[]; isAdding: boolean; typeAhead?: string[]; + createRoleBinding: (roleBinding: RoleBindingKind) => Promise; + deleteRoleBinding: (name: string, namespace: string) => Promise; onDismissNewRow: () => void; onError: (error: Error) => void; refresh: () => void; @@ -43,6 +45,8 @@ const RoleBindingPermissionsTable: React.FC = typeAhead, isProjectSubject, isAdding, + createRoleBinding, + deleteRoleBinding, onDismissNewRow, onError, refresh, diff --git a/frontend/src/concepts/roleBinding/RoleBindingPermissionsTableSection.tsx b/frontend/src/concepts/roleBinding/RoleBindingPermissionsTableSection.tsx index 269229df5d..8c4b8b30d0 100644 --- a/frontend/src/concepts/roleBinding/RoleBindingPermissionsTableSection.tsx +++ b/frontend/src/concepts/roleBinding/RoleBindingPermissionsTableSection.tsx @@ -10,7 +10,7 @@ import { StackItem, Title, } from '@patternfly/react-core'; -import { K8sResourceCommon } from '@openshift/dynamic-plugin-sdk-utils'; +import { K8sResourceCommon, K8sStatus } from '@openshift/dynamic-plugin-sdk-utils'; import { RoleBindingKind, RoleBindingRoleRef, RoleBindingSubject } from '~/k8sTypes'; import HeaderIcon from '~/concepts/design/HeaderIcon'; import { ProjectObjectType } from '~/concepts/design/utils'; @@ -29,6 +29,8 @@ export type RoleBindingPermissionsTableSectionAltProps = { description: string; }[]; typeAhead?: string[]; + createRoleBinding: (roleBinding: RoleBindingKind) => Promise; + deleteRoleBinding: (name: string, namespace: string) => Promise; refresh: () => void; typeModifier: string; defaultRoleBindingName?: string; @@ -45,6 +47,8 @@ const RoleBindingPermissionsTableSection: React.FC { refresh(); }} + createRoleBinding={createRoleBinding} + deleteRoleBinding={deleteRoleBinding} /> {error && ( diff --git a/frontend/src/pages/modelRegistrySettings/ModelRegistriesPermissions.tsx b/frontend/src/pages/modelRegistrySettings/ModelRegistriesPermissions.tsx index 5f468fdf30..cb5ab06806 100644 --- a/frontend/src/pages/modelRegistrySettings/ModelRegistriesPermissions.tsx +++ b/frontend/src/pages/modelRegistrySettings/ModelRegistriesPermissions.tsx @@ -19,6 +19,10 @@ import { SupportedArea } from '~/concepts/areas'; import { RoleBindingPermissionsRoleType } from '~/concepts/roleBinding/types'; import { useModelRegistryNamespaceCR } from '~/concepts/modelRegistry/context/useModelRegistryNamespaceCR'; import { AreaContext } from '~/concepts/areas/AreaContext'; +import { + createModelRegistryRoleBinding, + deleteModelRegistryRoleBinding, +} from '~/services/modelRegistrySettingsService'; import useModelRegistryRoleBindings from './useModelRegistryRoleBindings'; import ProjectsSettingsTab from './ProjectsTab/ProjectsSettingsTab'; @@ -121,6 +125,8 @@ const ModelRegistriesManagePermissions: React.FC = () => { } roleBindingPermissionsRB={{ ...roleBindings, data: filteredRoleBindings }} groups={groups} + createRoleBinding={createModelRegistryRoleBinding} + deleteRoleBinding={deleteModelRegistryRoleBinding} /> diff --git a/frontend/src/pages/modelRegistrySettings/ProjectsTab/ProjectsSettingsTab.tsx b/frontend/src/pages/modelRegistrySettings/ProjectsTab/ProjectsSettingsTab.tsx index b232866247..f0e893dc0e 100644 --- a/frontend/src/pages/modelRegistrySettings/ProjectsTab/ProjectsSettingsTab.tsx +++ b/frontend/src/pages/modelRegistrySettings/ProjectsTab/ProjectsSettingsTab.tsx @@ -22,6 +22,10 @@ import { ContextResourceData } from '~/types'; import { ProjectsContext } from '~/concepts/projects/ProjectsContext'; import { ProjectSectionID } from '~/pages/projects/screens/detail/types'; import RoleBindingPermissionsTableSection from '~/concepts/roleBinding/RoleBindingPermissionsTableSection'; +import { + createModelRegistryRoleBinding, + deleteModelRegistryRoleBinding, +} from '~/services/modelRegistrySettingsService'; type RoleBindingProjectPermissionsProps = { ownerReference?: K8sResourceCommon; @@ -115,6 +119,8 @@ const ProjectsSettingsTab: React.FC = ({ refresh={refreshRB} typeModifier="project" isProjectSubject={isProjectSubject} + createRoleBinding={createModelRegistryRoleBinding} + deleteRoleBinding={deleteModelRegistryRoleBinding} /> diff --git a/frontend/src/pages/modelRegistrySettings/useModelRegistryRoleBindings.ts b/frontend/src/pages/modelRegistrySettings/useModelRegistryRoleBindings.ts index 8aaa07200a..e5d1cba680 100644 --- a/frontend/src/pages/modelRegistrySettings/useModelRegistryRoleBindings.ts +++ b/frontend/src/pages/modelRegistrySettings/useModelRegistryRoleBindings.ts @@ -1,24 +1,18 @@ import * as React from 'react'; -import { listRoleBindings } from '~/api'; -import { AreaContext } from '~/concepts/areas/AreaContext'; -import { KnownLabels, RoleBindingKind } from '~/k8sTypes'; +import { RoleBindingKind } from '~/k8sTypes'; +import { listModelRegistryRoleBindings } from '~/services/modelRegistrySettingsService'; import useFetchState, { FetchState } from '~/utilities/useFetchState'; const useModelRegistryRoleBindings = (): FetchState => { - const { dscStatus } = React.useContext(AreaContext); - const getRoleBindings = React.useCallback( () => - listRoleBindings( - dscStatus?.components?.modelregistry?.registriesNamespace, - KnownLabels.LABEL_SELECTOR_MODEL_REGISTRY, - ).catch((e) => { + listModelRegistryRoleBindings().catch((e) => { if (e.statusObject?.code === 404) { throw new Error('No rolebindings found.'); } throw e; }), - [dscStatus?.components?.modelregistry?.registriesNamespace], + [], ); return useFetchState(getRoleBindings, []); diff --git a/frontend/src/pages/projects/projectSharing/ProjectSharing.tsx b/frontend/src/pages/projects/projectSharing/ProjectSharing.tsx index 9c0e500031..08556bdef3 100644 --- a/frontend/src/pages/projects/projectSharing/ProjectSharing.tsx +++ b/frontend/src/pages/projects/projectSharing/ProjectSharing.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext'; import RoleBindingPermissions from '~/concepts/roleBinding/RoleBindingPermissions'; import { RoleBindingPermissionsRoleType } from '~/concepts/roleBinding/types'; +import { createRoleBinding, deleteRoleBinding } from '~/api'; const ProjectSharing: React.FC = () => { const { @@ -27,6 +28,8 @@ const ProjectSharing: React.FC = () => { description="Add users and groups that can access the project." roleBindingPermissionsRB={projectSharingRB} groups={groups} + createRoleBinding={createRoleBinding} + deleteRoleBinding={deleteRoleBinding} /> ); }; diff --git a/frontend/src/services/modelRegistrySettingsService.ts b/frontend/src/services/modelRegistrySettingsService.ts index c77cf3b2f7..465ee7aa84 100644 --- a/frontend/src/services/modelRegistrySettingsService.ts +++ b/frontend/src/services/modelRegistrySettingsService.ts @@ -1,8 +1,10 @@ +import { K8sStatus } from '@openshift/dynamic-plugin-sdk-utils'; import axios from '~/utilities/axios'; -import { ModelRegistryKind } from '~/k8sTypes'; +import { ModelRegistryKind, RoleBindingKind } from '~/k8sTypes'; import { RecursivePartial } from '~/typeHelpers'; const registriesUrl = '/api/modelRegistries'; +const mrRoleBindingsUrl = '/api/modelRegistryRoleBindings'; type ModelRegistryAndDBPassword = { modelRegistry: ModelRegistryKind; @@ -55,3 +57,27 @@ export const deleteModelRegistryBackend = (modelRegistryName: string): Promise { throw new Error(e.response.data.message); }); + +export const listModelRegistryRoleBindings = (): Promise => + axios + .get(mrRoleBindingsUrl) + .then((response) => response.data.items) + .catch((e) => { + throw new Error(e.response.data.message); + }); + +export const createModelRegistryRoleBinding = (data: RoleBindingKind): Promise => + axios + .post(mrRoleBindingsUrl, data) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + }); + +export const deleteModelRegistryRoleBinding = (roleBindingName: string): Promise => + axios + .delete(`${mrRoleBindingsUrl}/${roleBindingName}`) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + });