From 3926db910b3a5842ad57bf97028dde15395f5993 Mon Sep 17 00:00:00 2001 From: ppadti Date: Tue, 7 Jan 2025 19:00:08 +0530 Subject: [PATCH] API submit logic and enabling feature flag by default --- .../src/routes/api/modelRegistries/index.ts | 27 +++--- .../api/modelRegistries/modelRegistryUtils.ts | 64 +++++++++++++- backend/src/types.ts | 15 +++- backend/src/utils/constants.ts | 2 +- docs/dashboard-config.md | 2 +- frontend/src/__mocks__/mockDashboardConfig.ts | 2 +- frontend/src/concepts/areas/const.ts | 2 +- frontend/src/k8sTypes.ts | 15 +++- .../CreateMRSecureDBSection.tsx | 62 ++++++++------ .../modelRegistrySettings/CreateModal.tsx | 84 ++++++++++++++----- .../modelRegistrySettings/PemFileUpload.tsx | 1 + .../src/pages/modelRegistrySettings/const.ts | 15 ++++ .../src/pages/modelRegistrySettings/utils.ts | 53 ++++++++++++ .../services/modelRegistrySettingsService.ts | 11 +-- .../odhdashboardconfig.yaml | 1 + 15 files changed, 285 insertions(+), 71 deletions(-) create mode 100644 frontend/src/pages/modelRegistrySettings/const.ts create mode 100644 frontend/src/pages/modelRegistrySettings/utils.ts diff --git a/backend/src/routes/api/modelRegistries/index.ts b/backend/src/routes/api/modelRegistries/index.ts index e4897a9fa6..aeee7bedd2 100644 --- a/backend/src/routes/api/modelRegistries/index.ts +++ b/backend/src/routes/api/modelRegistries/index.ts @@ -3,18 +3,19 @@ import { secureAdminRoute } from '../../../utils/route-security'; import { KubeFastifyInstance, ModelRegistryKind, RecursivePartial } from '../../../types'; import createError from 'http-errors'; import { - createModelRegistryAndSecret, + createModelRegistryAndCredentials, deleteModelRegistryAndSecret, getDatabasePassword, getModelRegistry, getModelRegistryNamespace, listModelRegistries, - patchModelRegistryAndUpdatePassword, + patchModelRegistryAndUpdateCredentials, } from './modelRegistryUtils'; -type ModelRegistryAndDBPassword = { +type ModelRegistryAndCredentials = { modelRegistry: ModelRegistryKind; databasePassword?: string; + newDatabaseCACertificate?: string; }; // Lists ModelRegistries directly (does not look up passwords from associated Secrets, you must make a direct request to '/:modelRegistryName' for that) @@ -48,19 +49,20 @@ export default async (fastify: KubeFastifyInstance): Promise => { async ( request: FastifyRequest<{ Querystring: { dryRun?: string }; - Body: ModelRegistryAndDBPassword; + Body: ModelRegistryAndCredentials; }>, reply: FastifyReply, ) => { const { dryRun } = request.query; - const { modelRegistry, databasePassword } = request.body; + const { modelRegistry, databasePassword, newDatabaseCACertificate } = request.body; try { const modelRegistryNamespace = getModelRegistryNamespace(fastify); - return createModelRegistryAndSecret( + return createModelRegistryAndCredentials( fastify, modelRegistry, modelRegistryNamespace, databasePassword, + newDatabaseCACertificate, !!dryRun, ).catch((e) => { throw createError(e.statusCode, e?.body?.message); @@ -98,7 +100,7 @@ export default async (fastify: KubeFastifyInstance): Promise => { modelRegistry, modelRegistryNamespace, ); - return { modelRegistry, databasePassword } satisfies ModelRegistryAndDBPassword; + return { modelRegistry, databasePassword } satisfies ModelRegistryAndCredentials; } catch (e) { fastify.log.error( `ModelRegistry ${modelRegistryName} could not be read, ${ @@ -120,21 +122,26 @@ export default async (fastify: KubeFastifyInstance): Promise => { request: FastifyRequest<{ Querystring: { dryRun?: string }; Params: { modelRegistryName: string }; - Body: RecursivePartial; + Body: RecursivePartial; }>, reply: FastifyReply, ) => { const { dryRun } = request.query; const { modelRegistryName } = request.params; - const { modelRegistry: patchBody, databasePassword } = request.body; + const { + modelRegistry: patchBody, + databasePassword, + newDatabaseCACertificate, + } = request.body; try { const modelRegistryNamespace = getModelRegistryNamespace(fastify); - const modelRegistry = await patchModelRegistryAndUpdatePassword( + const modelRegistry = await patchModelRegistryAndUpdateCredentials( fastify, modelRegistryName, modelRegistryNamespace, patchBody, databasePassword, + newDatabaseCACertificate, !!dryRun, ); return { modelRegistry, databasePassword }; diff --git a/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts b/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts index 7c5e63bcf1..5120cee92a 100644 --- a/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts +++ b/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts @@ -1,5 +1,5 @@ import { KubeFastifyInstance, ModelRegistryKind, RecursivePartial } from '../../../types'; -import { PatchUtils, V1Secret, V1Status } from '@kubernetes/client-node'; +import { PatchUtils, V1ConfigMap, V1Secret, V1Status } from '@kubernetes/client-node'; import { getClusterStatus } from '../../../utils/resourceUtils'; const MODEL_REGISTRY_API_GROUP = 'modelregistry.opendatahub.io'; @@ -130,13 +130,51 @@ const createModelRegistry = async ( return response.body; }; -export const createModelRegistryAndSecret = async ( +const createConfigMapForCACertficate = async ( + fastify: KubeFastifyInstance, + namespace: string, + name: string, + newDatabaseCACertificate: string, +): Promise => { + const body: V1ConfigMap = { + metadata: { + name, + namespace, + }, + data: { 'ca.crt': newDatabaseCACertificate }, + }; + return await fastify.kube.coreV1Api + .createNamespacedConfigMap(namespace, body) + .then((response) => response.body); +}; + +export const createModelRegistryAndCredentials = async ( fastify: KubeFastifyInstance, modelRegistry: ModelRegistryKind, modelRegistryNamespace: string, databasePassword?: string, + newDatabaseCACertificate?: string, dryRunOnly = false, ): Promise => { + let newCACertificateConfigMap: { name: string; key: string }; + if (newDatabaseCACertificate) { + const newConfigMap = await createConfigMapForCACertficate( + fastify, + modelRegistryNamespace, + modelRegistry.spec.mysql.sslRootCertificateConfigMap.name, + newDatabaseCACertificate, + ); + + newCACertificateConfigMap = { + name: newConfigMap.metadata.name, + key: Object.keys(newConfigMap.data || {})[0], + }; + + if (modelRegistry.spec.mysql) { + modelRegistry.spec.mysql.sslRootCertificateConfigMap = newCACertificateConfigMap; + } + } + const createBoth = async (dryRun = false) => { const dbSpec = getDatabaseSpec(modelRegistry); const newSecret = @@ -288,14 +326,34 @@ const updateDatabasePassword = async ( } }; -export const patchModelRegistryAndUpdatePassword = async ( +export const patchModelRegistryAndUpdateCredentials = async ( fastify: KubeFastifyInstance, modelRegistryName: string, modelRegistryNamespace: string, patchBody: RecursivePartial, databasePassword?: string, + newDatabaseCACertificate?: string, dryRunOnly = false, ): Promise => { + let newCACertificateConfigMap: { name: string; key: string }; + if (newDatabaseCACertificate) { + const newConfigMap = await createConfigMapForCACertficate( + fastify, + modelRegistryNamespace, + patchBody.spec.mysql.sslRootCertificateConfigMap.name, + newDatabaseCACertificate, + ); + + newCACertificateConfigMap = { + name: newConfigMap.metadata.name, + key: Object.keys(newConfigMap.data || {})[0], + }; + + if (patchBody.spec.mysql) { + patchBody.spec.mysql.sslRootCertificateConfigMap = newCACertificateConfigMap; + } + } + const patchBoth = async (dryRun = false) => { const modelRegistry = await patchModelRegistry( fastify, diff --git a/backend/src/types.ts b/backend/src/types.ts index df185bfa40..89cb01da13 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1201,7 +1201,20 @@ export type ModelRegistryKind = K8sResourceCommon & { port?: number; skipDBCreation?: boolean; username?: string; - }; + } & EitherNotBoth< + { + sslRootCertificateConfigMap?: { + name: string; + key: string; + }; + }, + { + sslRootCertificateSecret?: { + name: string; + key: string; + }; + } + >; }, { postgres?: { diff --git a/backend/src/utils/constants.ts b/backend/src/utils/constants.ts index 1091725da0..3998d7fa03 100644 --- a/backend/src/utils/constants.ts +++ b/backend/src/utils/constants.ts @@ -65,7 +65,7 @@ export const blankDashboardCR: DashboardConfig = { disableHardwareProfiles: true, disableDistributedWorkloads: false, disableModelRegistry: false, - disableModelRegistrySecureDB: true, + disableModelRegistrySecureDB: false, disableServingRuntimeParams: false, disableConnectionTypes: false, disableStorageClasses: false, diff --git a/docs/dashboard-config.md b/docs/dashboard-config.md index ef40f7304b..38fd441331 100644 --- a/docs/dashboard-config.md +++ b/docs/dashboard-config.md @@ -36,7 +36,7 @@ The following are a list of features that are supported, along with there defaul | disablePerformanceMetrics | false | Disables Endpoint Performance tab from Model Serving metrics. | | disableDistributedWorkloads | false | Disables Distributed Workload Metrics from the dashboard. | | disableModelRegistry | false | Disables Model Registry from the dashboard. | -| disableModelRegistrySecureDB | true | Disables Model Registry Secure DB from the dashboard. | +| disableModelRegistrySecureDB | false | Disables Model Registry Secure DB from the dashboard. | | disableServingRuntimeParams | false | Disables Serving Runtime params from the dashboard. | | disableStorageClasses | false | Disables storage classes settings nav item from the dashboard. | | disableNIMModelServing | true | Disables components of NIM Model UI from the dashboard. | diff --git a/frontend/src/__mocks__/mockDashboardConfig.ts b/frontend/src/__mocks__/mockDashboardConfig.ts index eaa8b4919b..75dfbfe216 100644 --- a/frontend/src/__mocks__/mockDashboardConfig.ts +++ b/frontend/src/__mocks__/mockDashboardConfig.ts @@ -60,7 +60,7 @@ export const mockDashboardConfig = ({ disableTrustyBiasMetrics = false, disableDistributedWorkloads = false, disableModelRegistry = false, - disableModelRegistrySecureDB = true, + disableModelRegistrySecureDB = false, disableServingRuntimeParams = false, disableConnectionTypes = true, disableStorageClasses = false, diff --git a/frontend/src/concepts/areas/const.ts b/frontend/src/concepts/areas/const.ts index 39b4809034..4730700409 100644 --- a/frontend/src/concepts/areas/const.ts +++ b/frontend/src/concepts/areas/const.ts @@ -28,7 +28,7 @@ export const allFeatureFlags: string[] = Object.keys({ disableHardwareProfiles: false, disableDistributedWorkloads: false, disableModelRegistry: false, - disableModelRegistrySecureDB: true, + disableModelRegistrySecureDB: false, disableServingRuntimeParams: false, disableConnectionTypes: false, disableStorageClasses: false, diff --git a/frontend/src/k8sTypes.ts b/frontend/src/k8sTypes.ts index e6659ca8aa..521fecf8ab 100644 --- a/frontend/src/k8sTypes.ts +++ b/frontend/src/k8sTypes.ts @@ -1322,7 +1322,20 @@ export type ModelRegistryKind = K8sResourceCommon & { port?: number; skipDBCreation?: boolean; username?: string; - }; + } & EitherNotBoth< + { + sslRootCertificateConfigMap?: { + name: string; + key: string; + } | null; + }, + { + sslRootCertificateSecret?: { + name: string; + key: string; + } | null; + } + >; }, { postgres?: { diff --git a/frontend/src/pages/modelRegistrySettings/CreateMRSecureDBSection.tsx b/frontend/src/pages/modelRegistrySettings/CreateMRSecureDBSection.tsx index c7bdf4ebcf..c1046d0caf 100644 --- a/frontend/src/pages/modelRegistrySettings/CreateMRSecureDBSection.tsx +++ b/frontend/src/pages/modelRegistrySettings/CreateMRSecureDBSection.tsx @@ -1,33 +1,32 @@ import React, { useState } from 'react'; import { FormGroup, Radio, Alert, MenuItem, MenuGroup, Tooltip } from '@patternfly/react-core'; import SearchSelector from '~/components/searchSelector/SearchSelector'; -import { translateDisplayNameForK8s } from '~/concepts/k8s/utils'; import { RecursivePartial } from '~/typeHelpers'; import { ConfigSecretItem } from '~/k8sTypes'; import { ODH_PRODUCT_NAME } from '~/utilities/const'; import { PemFileUpload } from './PemFileUpload'; - -export enum SecureDBRType { - CLUSTER_WIDE = 'cluster-wide', - OPENSHIFT = 'openshift', - EXISTING = 'existing', - NEW = 'new', -} +import { + CA_BUNDLE_CRT, + ODH_CA_BUNDLE_CRT, + ODH_TRUSTED_BUNDLE, + ResourceType, + SecureDBRType, +} from './const'; export interface SecureDBInfo { type: SecureDBRType; - configMap: string; + resourceName: string; key: string; certificate: string; nameSpace: string; isValid: boolean; - resourceType?: 'ConfigMap' | 'Secret'; + resourceType?: ResourceType; } interface CreateMRSecureDBSectionProps { secureDBInfo: SecureDBInfo; modelRegistryNamespace: string; - nameDesc: { name: string }; + k8sName: string; existingCertConfigMaps: ConfigSecretItem[]; existingCertSecrets: ConfigSecretItem[]; setSecureDBInfo: (info: SecureDBInfo) => void; @@ -36,16 +35,25 @@ interface CreateMRSecureDBSectionProps { export const CreateMRSecureDBSection: React.FC = ({ secureDBInfo, modelRegistryNamespace, - nameDesc, + k8sName, existingCertConfigMaps, existingCertSecrets, setSecureDBInfo, }) => { const [searchConfigSecretName, setSearchConfigSecretName] = useState(''); const [searchKey, setSearchKey] = useState(''); - const ODH_TRUSTED_BUNDLE = 'odh-trusted-ca-bundle'; - const CA_BUNDLE_CRT = 'ca-bundle.crt'; - const ODH_CA_BUNDLE_CRT = 'odh-ca-bundle.crt'; + + let newConfigMapName = 'db-credential'; + if (k8sName) { + newConfigMapName = `${k8sName}-db-credential`; + let suffix = 1; + const existingConfigMaps = existingCertConfigMaps.map((cofigmap) => cofigmap.name); + + while (existingConfigMaps.includes(newConfigMapName)) { + suffix++; + newConfigMapName = `${k8sName}-db-credential-${suffix}`; + } + } const hasContent = (value: string): boolean => !!value.trim().length; @@ -55,7 +63,7 @@ export const CreateMRSecureDBSection: React.FC = ( return true; } if (fullInfo.type === SecureDBRType.EXISTING) { - return hasContent(fullInfo.configMap) && hasContent(fullInfo.key); + return hasContent(fullInfo.resourceName) && hasContent(fullInfo.key); } if (fullInfo.type === SecureDBRType.NEW) { return hasContent(fullInfo.certificate); @@ -88,7 +96,7 @@ export const CreateMRSecureDBSection: React.FC = ( type, nameSpace: '', key: '', - configMap: '', + resourceName: '', resourceType: undefined, certificate: '', }; @@ -98,12 +106,12 @@ export const CreateMRSecureDBSection: React.FC = ( }); }; - const handleResourceSelect = (selectedName: string, resourceType: 'ConfigMap' | 'Secret') => { + const handleResourceSelect = (selectedName: string, resourceType: ResourceType) => { setSearchConfigSecretName(''); const newInfo = { ...secureDBInfo, - configMap: selectedName, + resourceName: selectedName, key: '', resourceType, }; @@ -121,7 +129,7 @@ export const CreateMRSecureDBSection: React.FC = ( .map((configMap, index) => ( handleResourceSelect(configMap.name, 'ConfigMap')} + onClick={() => handleResourceSelect(configMap.name, ResourceType.ConfigMap)} > {configMap.name} @@ -135,7 +143,7 @@ export const CreateMRSecureDBSection: React.FC = ( .map((secret, index) => ( handleResourceSelect(secret.name, 'Secret')} + onClick={() => handleResourceSelect(secret.name, ResourceType.Secret)} > {secret.name} @@ -239,7 +247,7 @@ export const CreateMRSecureDBSection: React.FC = ( onSearchChange={(newValue) => setSearchConfigSecretName(newValue)} onSearchClear={() => setSearchConfigSecretName('')} searchValue={searchConfigSecretName} - toggleText={secureDBInfo.configMap || 'Select a ConfigMap or a Secret'} + toggleText={secureDBInfo.resourceName || 'Select a ConfigMap or a Secret'} > {getFilteredExistingCAResources()} @@ -254,12 +262,12 @@ export const CreateMRSecureDBSection: React.FC = ( isFullWidth dataTestId="existing-ca-key-selector" onSearchChange={(newValue) => setSearchKey(newValue)} - isDisabled={!secureDBInfo.configMap} + isDisabled={!secureDBInfo.resourceName} onSearchClear={() => setSearchKey('')} searchValue={searchKey} toggleText={ secureDBInfo.key || - (!secureDBInfo.configMap + (!secureDBInfo.resourceName ? 'Select a resource to view its available keys' : 'Select a key') } @@ -268,7 +276,7 @@ export const CreateMRSecureDBSection: React.FC = ( secureDBInfo.resourceType === 'ConfigMap' ? existingCertConfigMaps : existingCertSecrets, - secureDBInfo.configMap, + secureDBInfo.resourceName, ) .filter((item) => item.toLowerCase().includes(searchKey.toLowerCase())) .map((item, index) => ( @@ -305,8 +313,7 @@ export const CreateMRSecureDBSection: React.FC = ( variant="info" style={{ marginLeft: 'var(--pf-t--global--spacer--lg)' }} > - Uploading a certificate below creates the{' '} - {translateDisplayNameForK8s(nameDesc.name)}-db-credential ConfigMap + Uploading a certificate below creates the {newConfigMapName} ConfigMap with the ca.crt key. If you'd like to upload the certificate as a Secret instead, see the documentation for more details. @@ -319,6 +326,7 @@ export const CreateMRSecureDBSection: React.FC = ( onChange={(value) => { const newInfo = { ...secureDBInfo, + resourceName: newConfigMapName, certificate: value, }; setSecureDBInfo({ ...newInfo, isValid: isValid(newInfo) }); diff --git a/frontend/src/pages/modelRegistrySettings/CreateModal.tsx b/frontend/src/pages/modelRegistrySettings/CreateModal.tsx index 0dac12743f..16c1b4af9b 100644 --- a/frontend/src/pages/modelRegistrySettings/CreateModal.tsx +++ b/frontend/src/pages/modelRegistrySettings/CreateModal.tsx @@ -27,8 +27,15 @@ import K8sNameDescriptionField, { useK8sNameDescriptionFieldData, } from '~/concepts/k8s/K8sNameDescriptionField/K8sNameDescriptionField'; import useModelRegistryCertificateNames from '~/concepts/modelRegistrySettings/useModelRegistryCertificateNames'; -import { CreateMRSecureDBSection, SecureDBInfo, SecureDBRType } from './CreateMRSecureDBSection'; +import { + findConfigMap, + findSecureDBType, + constructRequestBody, +} from '~/pages/modelRegistrySettings/utils'; +import { RecursivePartial } from '~/typeHelpers'; +import { CreateMRSecureDBSection, SecureDBInfo } from './CreateMRSecureDBSection'; import ModelRegistryDatabasePassword from './ModelRegistryDatabasePassword'; +import { ResourceType, SecureDBRType } from './const'; type CreateModalProps = { onClose: () => void; @@ -51,7 +58,7 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist const [secureDBInfo, setSecureDBInfo] = React.useState({ type: SecureDBRType.CLUSTER_WIDE, nameSpace: '', - configMap: '', + resourceName: '', certificate: '', key: '', isValid: true, @@ -77,6 +84,22 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist setPort(dbSpec?.port?.toString() || 'Unknown'); setUsername(dbSpec?.username || 'Unknown'); setDatabase(dbSpec?.database || 'Unknown'); + const certificateResourceRef = + mr.spec.mysql?.sslRootCertificateConfigMap || mr.spec.mysql?.sslRootCertificateSecret; + if (certificateResourceRef) { + setAddSecureDB(true); + const existingInfo = { + type: findSecureDBType(certificateResourceRef.name, certificateResourceRef.key), + nameSpace: '', + key: certificateResourceRef.key, + resourceName: certificateResourceRef.name, + resourceType: mr.spec.mysql?.sslRootCertificateSecret + ? ResourceType.Secret + : ResourceType.ConfigMap, + certificate: '', + }; + setSecureDBInfo({ ...existingInfo, isValid: true }); + } } }, [mr]); @@ -102,26 +125,32 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist setIsSubmitting(true); setError(undefined); + const newDatabaseCACertificate = + addSecureDB && secureDBInfo.type === SecureDBRType.NEW ? secureDBInfo.certificate : undefined; + if (mr) { + const data: RecursivePartial = { + metadata: { + annotations: { + 'openshift.io/description': nameDesc.description, + 'openshift.io/display-name': nameDesc.name.trim(), + }, + }, + spec: { + mysql: { + host, + port: Number(port), + database, + username, + }, + }, + }; + try { await updateModelRegistryBackend(mr.metadata.name, { - modelRegistry: { - metadata: { - annotations: { - 'openshift.io/description': nameDesc.description, - 'openshift.io/display-name': nameDesc.name.trim(), - }, - }, - spec: { - mysql: { - host, - port: Number(port), - database, - username, - }, - }, - }, + modelRegistry: constructRequestBody(data, secureDBInfo, addSecureDB), databasePassword: password, + newDatabaseCACertificate, }); await refresh(); onBeforeClose(); @@ -161,8 +190,22 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist }, }, }; + + if (addSecureDB && secureDBInfo.resourceType === ResourceType.Secret && data.spec.mysql) { + data.spec.mysql.sslRootCertificateSecret = { + name: secureDBInfo.resourceName, + key: secureDBInfo.key, + }; + } else if (addSecureDB && data.spec.mysql) { + data.spec.mysql.sslRootCertificateConfigMap = findConfigMap(secureDBInfo); + } + try { - await createModelRegistryBackend({ modelRegistry: data, databasePassword: password }); + await createModelRegistryBackend({ + modelRegistry: data, + databasePassword: password, + newDatabaseCACertificate, + }); await refresh(); onBeforeClose(); } catch (e) { @@ -313,6 +356,7 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist onChange={(_e, value) => setAddSecureDB(value)} id="add-secure-db" name="add-secure-db" + data-testid="add-secure-db-checkbox" /> {addSecureDB && @@ -322,7 +366,7 @@ const CreateModal: React.FC = ({ onClose, refresh, modelRegist void }> = ({ }; const handleClear = () => { + onChange(''); setFilename(''); setValue(''); setIsRejected(false); diff --git a/frontend/src/pages/modelRegistrySettings/const.ts b/frontend/src/pages/modelRegistrySettings/const.ts new file mode 100644 index 0000000000..122aa6e596 --- /dev/null +++ b/frontend/src/pages/modelRegistrySettings/const.ts @@ -0,0 +1,15 @@ +export const ODH_TRUSTED_BUNDLE = 'odh-trusted-ca-bundle'; +export const CA_BUNDLE_CRT = 'ca-bundle.crt'; +export const ODH_CA_BUNDLE_CRT = 'odh-ca-bundle.crt'; + +export enum SecureDBRType { + CLUSTER_WIDE = 'cluster-wide', + OPENSHIFT = 'openshift', + EXISTING = 'existing', + NEW = 'new', +} + +export enum ResourceType { + ConfigMap = 'ConfigMap', + Secret = 'Secret', +} diff --git a/frontend/src/pages/modelRegistrySettings/utils.ts b/frontend/src/pages/modelRegistrySettings/utils.ts new file mode 100644 index 0000000000..e268a1e686 --- /dev/null +++ b/frontend/src/pages/modelRegistrySettings/utils.ts @@ -0,0 +1,53 @@ +import { RecursivePartial } from '~/typeHelpers'; +import { ModelRegistryKind } from '~/k8sTypes'; +import { + CA_BUNDLE_CRT, + ODH_CA_BUNDLE_CRT, + ODH_TRUSTED_BUNDLE, + ResourceType, + SecureDBRType, +} from './const'; +import { SecureDBInfo } from './CreateMRSecureDBSection'; + +export const findSecureDBType = (name: string, key: string): SecureDBRType => { + if (name === ODH_TRUSTED_BUNDLE && key === CA_BUNDLE_CRT) { + return SecureDBRType.CLUSTER_WIDE; + } + if (name === ODH_TRUSTED_BUNDLE && key === ODH_CA_BUNDLE_CRT) { + return SecureDBRType.OPENSHIFT; + } + return SecureDBRType.EXISTING; +}; + +export const findConfigMap = (secureDBInfo: SecureDBInfo): { name: string; key: string } => { + if (secureDBInfo.type === SecureDBRType.CLUSTER_WIDE) { + return { name: ODH_TRUSTED_BUNDLE, key: CA_BUNDLE_CRT }; + } + if (secureDBInfo.type === SecureDBRType.OPENSHIFT) { + return { name: ODH_TRUSTED_BUNDLE, key: ODH_CA_BUNDLE_CRT }; + } + return { name: secureDBInfo.resourceName, key: secureDBInfo.key }; +}; + +export const constructRequestBody = ( + data: RecursivePartial, + secureDBInfo: SecureDBInfo, + addSecureDB: boolean, +): RecursivePartial => { + const mr = data; + if (addSecureDB && secureDBInfo.resourceType === ResourceType.Secret && mr.spec?.mysql) { + mr.spec.mysql.sslRootCertificateSecret = { + name: secureDBInfo.resourceName, + key: secureDBInfo.key, + }; + mr.spec.mysql.sslRootCertificateConfigMap = null; + } else if (addSecureDB && mr.spec?.mysql) { + mr.spec.mysql.sslRootCertificateConfigMap = findConfigMap(secureDBInfo); + mr.spec.mysql.sslRootCertificateSecret = null; + } else if (!addSecureDB && mr.spec?.mysql) { + mr.spec.mysql.sslRootCertificateConfigMap = null; + mr.spec.mysql.sslRootCertificateSecret = null; + } + + return mr; +}; diff --git a/frontend/src/services/modelRegistrySettingsService.ts b/frontend/src/services/modelRegistrySettingsService.ts index 71ba133842..da121bd599 100644 --- a/frontend/src/services/modelRegistrySettingsService.ts +++ b/frontend/src/services/modelRegistrySettingsService.ts @@ -8,9 +8,10 @@ const registriesUrl = '/api/modelRegistries'; const mrRoleBindingsUrl = '/api/modelRegistryRoleBindings'; const configSecretsUrl = '/api/modelRegistryCertificates'; -type ModelRegistryAndDBPassword = { +export type ModelRegistryAndCredentials = { modelRegistry: ModelRegistryKind; databasePassword?: string; + newDatabaseCACertificate?: string; }; export const listModelRegistriesBackend = (labelSelector?: string): Promise => @@ -22,7 +23,7 @@ export const listModelRegistriesBackend = (labelSelector?: string): Promise => axios .post(registriesUrl, data) @@ -33,7 +34,7 @@ export const createModelRegistryBackend = ( export const getModelRegistryBackend = ( modelRegistryName: string, -): Promise => +): Promise => axios .get(`${registriesUrl}/${modelRegistryName}`) .then((response) => response.data) @@ -43,8 +44,8 @@ export const getModelRegistryBackend = ( export const updateModelRegistryBackend = ( modelRegistryName: string, - patch: RecursivePartial, -): Promise => + patch: RecursivePartial, +): Promise => axios .patch(`${registriesUrl}/${modelRegistryName}`, patch) .then((response) => response.data) diff --git a/manifests/rhoai/shared/odhdashboardconfig/odhdashboardconfig.yaml b/manifests/rhoai/shared/odhdashboardconfig/odhdashboardconfig.yaml index fb43760b08..85033205b9 100644 --- a/manifests/rhoai/shared/odhdashboardconfig/odhdashboardconfig.yaml +++ b/manifests/rhoai/shared/odhdashboardconfig/odhdashboardconfig.yaml @@ -28,6 +28,7 @@ spec: disableModelMesh: false disableDistributedWorkloads: false disableModelRegistry: false + disableModelRegistrySecureDB: false disableServingRuntimeParams: false disableStorageClasses: false disableNIMModelServing: true