diff --git a/backend/src/plugins/kube.ts b/backend/src/plugins/kube.ts index 60610f7c61..491c69b9e9 100644 --- a/backend/src/plugins/kube.ts +++ b/backend/src/plugins/kube.ts @@ -3,9 +3,10 @@ import fp from 'fastify-plugin'; import { FastifyInstance } from 'fastify'; import * as jsYaml from 'js-yaml'; import * as k8s from '@kubernetes/client-node'; +import { User } from '@kubernetes/client-node/dist/config_types'; +import { errorHandler } from '../utils'; import { DEV_MODE } from '../utils/constants'; import { cleanupGPU, initializeWatchedResources } from '../utils/resourceUtils'; -import { User } from '@kubernetes/client-node/dist/config_types'; const CONSOLE_CONFIG_YAML_FIELD = 'console-config.yaml'; @@ -30,6 +31,9 @@ export default fp(async (fastify: FastifyInstance) => { let currentToken; try { + if (currentUser === null) { + throw new Error('Current user is null'); + } currentToken = await getCurrentToken(currentUser); } catch (e) { currentToken = ''; @@ -46,25 +50,20 @@ export default fp(async (fastify: FastifyInstance) => { ); clusterID = (clusterVersion.body as { spec: { clusterID: string } }).spec.clusterID; } catch (e) { - fastify.log.error( - e, - `Failed to retrieve cluster id: ${e.response?.body?.message || e.message}.`, - ); + fastify.log.error(e, `Failed to retrieve cluster id: ${errorHandler(e)}.`); } let clusterBranding = 'okd'; try { const consoleConfig = await coreV1Api .readNamespacedConfigMap('console-config', 'openshift-console') .then((result) => result.body); - if (consoleConfig?.data?.[CONSOLE_CONFIG_YAML_FIELD]) { + if (consoleConfig.data?.[CONSOLE_CONFIG_YAML_FIELD]) { const consoleConfigData = jsYaml.load(consoleConfig.data[CONSOLE_CONFIG_YAML_FIELD]); clusterBranding = consoleConfigData.customization?.branding || 'okd'; fastify.log.info(`Cluster Branding: ${clusterBranding}`); } } catch (e) { - fastify.log.error( - `Failed to retrieve console cluster info: ${e.response?.body?.message || e.message}`, - ); + fastify.log.error(`Failed to retrieve console cluster info: ${errorHandler(e)}`); } fastify.decorate('kube', { @@ -94,8 +93,8 @@ export default fp(async (fastify: FastifyInstance) => { ); }); -const getCurrentNamespace = async () => { - return new Promise((resolve, reject) => { +const getCurrentNamespace = async () => + new Promise((resolve, reject) => { if (currentContext === 'inClusterContext') { fs.readFile( '/var/run/secrets/kubernetes.io/serviceaccount/namespace', @@ -113,13 +112,13 @@ const getCurrentNamespace = async () => { resolve(currentContext.split('/')[0]); } }); -}; -const getCurrentToken = async (currentUser: User) => { - return new Promise((resolve, reject) => { +// eslint-disable-next-line @typescript-eslint/no-shadow +const getCurrentToken = async (currentUser: User) => + new Promise((resolve, reject) => { if (currentContext === 'inClusterContext') { const location = - currentUser?.authProvider?.config?.tokenFile || + currentUser.authProvider?.config?.tokenFile || '/var/run/secrets/kubernetes.io/serviceaccount/token'; fs.readFile(location, 'utf8', (err, data) => { if (err) { @@ -128,7 +127,6 @@ const getCurrentToken = async (currentUser: User) => { resolve(data); }); } else { - resolve(currentUser?.token || ''); + resolve(currentUser.token || ''); } }); -}; diff --git a/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts b/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts index 2e214f8a51..0f910ed2e9 100644 --- a/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts +++ b/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts @@ -1,14 +1,14 @@ -import { KubeFastifyInstance, AcceleratorProfileKind } from '../../../types'; import { FastifyRequest } from 'fastify'; import createError from 'http-errors'; +import { KubeFastifyInstance, AcceleratorProfileKind } from '../../../types'; import { translateDisplayNameForK8s } from '../../../utils/resourceUtils'; export const postAcceleratorProfile = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { - const customObjectsApi = fastify.kube.customObjectsApi; - const namespace = fastify.kube.namespace; + const customObjectsApi = fastify.kube?.customObjectsApi; + const namespace = fastify.kube?.namespace; const body = request.body as AcceleratorProfileKind['spec']; const payload: AcceleratorProfileKind = { @@ -16,7 +16,7 @@ export const postAcceleratorProfile = async ( kind: 'AcceleratorProfile', metadata: { name: translateDisplayNameForK8s(body.displayName), - namespace: namespace, + namespace, annotations: { 'opendatahub.io/modified-date': new Date().toISOString(), }, @@ -25,22 +25,38 @@ export const postAcceleratorProfile = async ( }; try { + if (typeof customObjectsApi === 'undefined') { + throw new Error('customObjectsApi is undefined'); + } await customObjectsApi .createNamespacedCustomObject( 'dashboard.opendatahub.io', 'v1', - namespace, + namespace ?? '', 'acceleratorprofiles', payload, ) .catch((e) => { throw createError(e.statusCode, e?.body?.message); }); - return { success: true, error: null }; + return { success: true, error: '' }; } catch (e) { - if (e.response?.statusCode !== 404) { + if ( + typeof e === 'object' && + !!e && + 'message' in e && + !!e.message && + typeof e.message === 'string' && + 'response' in e && + !!e.response && + typeof e.response === 'object' && + 'statusCode' in e.response && + !!e.response.statusCode && + typeof e.response.statusCode === 'number' && + e.response.statusCode !== 404 + ) { fastify.log.error(e, 'Unable to add accelerator profile.'); - return { success: false, error: 'Unable to add accelerator profile: ' + e.message }; + return { success: false, error: `Unable to add accelerator profile: ${e.message}` }; } throw e; } @@ -50,8 +66,11 @@ export const deleteAcceleratorProfile = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { - const customObjectsApi = fastify.kube.customObjectsApi; - const namespace = fastify.kube.namespace; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is not defined.'); + } + const { customObjectsApi } = fastify.kube; + const { namespace } = fastify.kube; const params = request.params as { acceleratorProfileName: string }; try { @@ -66,11 +85,24 @@ export const deleteAcceleratorProfile = async ( .catch((e) => { throw createError(e.statusCode, e?.body?.message); }); - return { success: true, error: null }; + return { success: true, error: '' }; } catch (e) { - if (e.response?.statusCode === 404) { + if ( + typeof e === 'object' && + !!e && + 'message' in e && + !!e.message && + typeof e.message === 'string' && + 'response' in e && + !!e.response && + typeof e.response === 'object' && + 'statusCode' in e.response && + !!e.response.statusCode && + typeof e.response.statusCode === 'number' && + e.response.statusCode !== 404 + ) { fastify.log.error(e, 'Unable to delete accelerator profile.'); - return { success: false, error: 'Unable to delete accelerator profile: ' + e.message }; + return { success: false, error: `Unable to delete accelerator profile: ${e.message}` }; } throw e; } @@ -80,8 +112,11 @@ export const updateAcceleratorProfile = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { - const customObjectsApi = fastify.kube.customObjectsApi; - const namespace = fastify.kube.namespace; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undefined'); + } + const { customObjectsApi } = fastify.kube; + const { namespace } = fastify.kube; const params = request.params as { acceleratorProfileName: string }; const body = request.body as Partial; @@ -139,11 +174,24 @@ export const updateAcceleratorProfile = async ( .catch((e) => { throw createError(e.statusCode, e?.body?.message); }); - return { success: true, error: null }; + return { success: true, error: '' }; } catch (e) { - if (e.response?.statusCode !== 404) { + if ( + typeof e === 'object' && + !!e && + 'message' in e && + !!e.message && + typeof e.message === 'string' && + 'response' in e && + !!e.response && + typeof e.response === 'object' && + 'statusCode' in e.response && + !!e.response.statusCode && + typeof e.response.statusCode === 'number' && + e.response.statusCode !== 404 + ) { fastify.log.error(e, 'Unable to update accelerator profile.'); - return { success: false, error: 'Unable to update accelerator profile: ' + e.message }; + return { success: false, error: `Unable to update accelerator profile: ${e.message}` }; } throw e; } diff --git a/backend/src/routes/api/accelerator-profiles/index.ts b/backend/src/routes/api/accelerator-profiles/index.ts index 984b6b527a..deac41016b 100644 --- a/backend/src/routes/api/accelerator-profiles/index.ts +++ b/backend/src/routes/api/accelerator-profiles/index.ts @@ -1,48 +1,42 @@ import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { secureAdminRoute } from '../../../utils/route-security'; import { deleteAcceleratorProfile, postAcceleratorProfile, updateAcceleratorProfile, } from './acceleratorProfilesUtils'; +import { secureAdminRoute } from '../../../utils/route-security'; export default async (fastify: FastifyInstance): Promise => { fastify.delete( '/:acceleratorProfileName', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return deleteAcceleratorProfile(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + deleteAcceleratorProfile(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); fastify.put( '/:acceleratorProfileName', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return updateAcceleratorProfile(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + updateAcceleratorProfile(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); fastify.post( '/', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return postAcceleratorProfile(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + postAcceleratorProfile(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/accelerators/index.ts b/backend/src/routes/api/accelerators/index.ts index 5fa1d4d917..90086b4562 100644 --- a/backend/src/routes/api/accelerators/index.ts +++ b/backend/src/routes/api/accelerators/index.ts @@ -1,5 +1,5 @@ -import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { getDetectedAccelerators } from './acceleratorUtils'; +import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { logRequestDetails } from '../../../utils/fileUtils'; export default async (fastify: KubeFastifyInstance): Promise => { diff --git a/backend/src/routes/api/builds/index.ts b/backend/src/routes/api/builds/index.ts index 8a497f6593..16f9f9800e 100644 --- a/backend/src/routes/api/builds/index.ts +++ b/backend/src/routes/api/builds/index.ts @@ -1,19 +1,17 @@ -import { KubeFastifyInstance } from '../../../types'; import { FastifyReply, FastifyRequest } from 'fastify'; import { listBuilds } from './list'; +import { KubeFastifyInstance } from '../../../types'; import { secureRoute } from '../../../utils/route-security'; module.exports = async (fastify: KubeFastifyInstance) => { fastify.get( '/', - secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return listBuilds() - .then((res) => { - return res; - }) + secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listBuilds() + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/builds/list.ts b/backend/src/routes/api/builds/list.ts index 964a26b07d..216c2fe62e 100644 --- a/backend/src/routes/api/builds/list.ts +++ b/backend/src/routes/api/builds/list.ts @@ -1,6 +1,4 @@ import { getBuildStatuses } from '../../../utils/resourceUtils'; import { BuildStatus } from '../../../types'; -export const listBuilds = async (): Promise => { - return Promise.resolve(getBuildStatuses()); -}; +export const listBuilds = async (): Promise => Promise.resolve(getBuildStatuses()); diff --git a/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts b/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts index e93d91ae7c..a166d76ba8 100644 --- a/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts +++ b/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts @@ -1,8 +1,9 @@ import { FastifyRequest } from 'fastify'; +import { V1ConfigMap } from '@kubernetes/client-node'; +import { errorHandler } from '../../../utils'; import { rolloutDeployment } from '../../../utils/deployment'; import { KubeFastifyInstance, ClusterSettings } from '../../../types'; import { getDashboardConfig } from '../../../utils/resourceUtils'; -import { V1ConfigMap } from '@kubernetes/client-node'; import { setDashboardConfig } from '../config/configUtils'; import { checkJupyterEnabled } from '../../../utils/componentUtils'; @@ -29,8 +30,8 @@ export const updateClusterSettings = async ( Body: ClusterSettings; }>, ): Promise<{ success: boolean; error: string }> => { - const coreV1Api = fastify.kube.coreV1Api; - const namespace = fastify.kube.namespace; + const coreV1Api = fastify.kube?.coreV1Api; + const namespace = fastify.kube?.namespace; const { pvcSize, cullerTimeout, @@ -58,7 +59,7 @@ export const updateClusterSettings = async ( await patchCM(fastify, segmentKeyCfg, { data: { segmentKeyEnabled: String(userTrackingEnabled) }, }).catch((e) => { - fastify.log.error('Failed to update segment key enabled: ' + e.message); + fastify.log.error(`Failed to update segment key enabled: ${e.message}`); }); if (pvcSize && cullerTimeout) { await setDashboardConfig(fastify, { @@ -66,12 +67,13 @@ export const updateClusterSettings = async ( notebookController: { enabled: isJupyterEnabled, pvcSize: `${pvcSize}Gi`, - ...(isJupyterEnabled && { - notebookTolerationSettings: { - enabled: notebookTolerationSettings.enabled, - key: notebookTolerationSettings.key, - }, - }), + ...(isJupyterEnabled && + !!notebookTolerationSettings && { + notebookTolerationSettings: { + enabled: notebookTolerationSettings.enabled, + key: notebookTolerationSettings.key, + }, + }), }, }, }); @@ -80,10 +82,13 @@ export const updateClusterSettings = async ( if (Number(cullerTimeout) === DEFAULT_CULLER_TIMEOUT) { isEnabled = false; } - if (!isEnabled) { - await coreV1Api.deleteNamespacedConfigMap(nbcCfg, fastify.kube.namespace).catch((e) => { - fastify.log.error('Failed to delete culler config: ') + e.message; - }); + if (!isEnabled && coreV1Api) { + await coreV1Api + .deleteNamespacedConfigMap(nbcCfg, fastify.kube?.namespace ?? '') + .catch((e) => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + fastify.log.error('Failed to delete culler config: ') + e.message; + }); } else { await patchCM(fastify, nbcCfg, { data: { ENABLE_CULLING: String(isEnabled), CULL_IDLE_TIME: String(cullingTimeMin) }, @@ -104,19 +109,35 @@ export const updateClusterSettings = async ( IDLENESS_CHECK_PERIOD: DEFAULT_IDLENESS_CHECK_PERIOD, //In minutes }, }; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undefined.'); + } await fastify.kube.coreV1Api.createNamespacedConfigMap(fastify.kube.namespace, cm); } else { - fastify.log.error('Failed to patch culler config: ' + e.message); + fastify.log.error(`Failed to patch culler config: ${e.message}`); } }); } } - await rolloutDeployment(fastify, namespace, 'notebook-controller-deployment'); - return { success: true, error: null }; + await rolloutDeployment(fastify, namespace ?? '', 'notebook-controller-deployment'); + return { success: true, error: '' }; } catch (e) { - fastify.log.error(e, 'Setting cluster settings error: ' + e.response?.body?.message); - if (e.response?.statusCode !== 404) { - return { success: false, error: 'Unable to update cluster settings. ' + e.message }; + fastify.log.error(e, `Setting cluster settings error: ${errorHandler(e)}`); + if ( + typeof e === 'object' && + !!e && + 'message' in e && + !!e.message && + typeof e.message === 'string' && + 'response' in e && + !!e.response && + typeof e.response === 'object' && + 'statusCode' in e.response && + !!e.response.statusCode && + typeof e.response.statusCode === 'number' && + e.response.statusCode !== 404 + ) { + return { success: false, error: `Unable to update cluster settings. ${e.message}` }; } throw e; } @@ -125,27 +146,39 @@ export const updateClusterSettings = async ( export const getClusterSettings = async ( fastify: KubeFastifyInstance, ): Promise => { - const coreV1Api = fastify.kube.coreV1Api; - const namespace = fastify.kube.namespace; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undefined.'); + } + const { coreV1Api } = fastify.kube; + const { namespace } = fastify.kube; const dashConfig = getDashboardConfig(); const isJupyterEnabled = checkJupyterEnabled(); - const clusterSettings: ClusterSettings = { - ...DEFAULT_CLUSTER_SETTINGS, - modelServingPlatformEnabled: { - kServe: !dashConfig.spec.dashboardConfig.disableKServe, - modelMesh: !dashConfig.spec.dashboardConfig.disableModelMesh, - }, - notebookTolerationSettings: { - ...DEFAULT_CLUSTER_SETTINGS.notebookTolerationSettings, - key: isJupyterEnabled ? DEFAULT_CLUSTER_SETTINGS.notebookTolerationSettings.key : '', - }, - }; + const clusterSettings: ClusterSettings = + DEFAULT_CLUSTER_SETTINGS.notebookTolerationSettings !== null + ? { + ...DEFAULT_CLUSTER_SETTINGS, + modelServingPlatformEnabled: { + kServe: !dashConfig.spec.dashboardConfig.disableKServe, + modelMesh: !dashConfig.spec.dashboardConfig.disableModelMesh, + }, + notebookTolerationSettings: { + ...DEFAULT_CLUSTER_SETTINGS.notebookTolerationSettings, + key: isJupyterEnabled ? DEFAULT_CLUSTER_SETTINGS.notebookTolerationSettings.key : '', + }, + } + : { + ...DEFAULT_CLUSTER_SETTINGS, + modelServingPlatformEnabled: { + kServe: !dashConfig.spec.dashboardConfig.disableKServe, + modelMesh: !dashConfig.spec.dashboardConfig.disableModelMesh, + }, + }; if (!dashConfig.spec.dashboardConfig.disableTracking) { try { const segmentEnabledRes = await coreV1Api.readNamespacedConfigMap(segmentKeyCfg, namespace); clusterSettings.userTrackingEnabled = - segmentEnabledRes.body.data.segmentKeyEnabled === 'true'; + segmentEnabledRes.body.data?.segmentKeyEnabled === 'true'; } catch (e) { fastify.log.error(e, 'Error retrieving segment key enabled.'); } @@ -153,18 +186,18 @@ export const getClusterSettings = async ( clusterSettings.pvcSize = DEFAULT_PVC_SIZE; if (dashConfig.spec.notebookController?.pvcSize) { - clusterSettings.pvcSize = Number(dashConfig.spec.notebookController?.pvcSize.replace('Gi', '')); + clusterSettings.pvcSize = Number(dashConfig.spec.notebookController.pvcSize.replace('Gi', '')); } if (dashConfig.spec.notebookController?.notebookTolerationSettings && isJupyterEnabled) { clusterSettings.notebookTolerationSettings = - dashConfig.spec.notebookController?.notebookTolerationSettings; + dashConfig.spec.notebookController.notebookTolerationSettings; } clusterSettings.cullerTimeout = DEFAULT_CULLER_TIMEOUT; await fastify.kube.coreV1Api .readNamespacedConfigMap(nbcCfg, fastify.kube.namespace) .then((res) => { - const cullerTimeout = res.body.data['CULL_IDLE_TIME']; - const isEnabled = Boolean(res.body.data['ENABLE_CULLING']); + const cullerTimeout = res.body.data?.CULL_IDLE_TIME; + const isEnabled = Boolean(res.body.data?.ENABLE_CULLING); if (isEnabled) { clusterSettings.cullerTimeout = Number(cullerTimeout) * 60; //minutes to seconds; } @@ -186,6 +219,9 @@ const patchCM = async ( name: string, patch: Partial, ): Promise => { + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undefined.'); + } const response = await fastify.kube.coreV1Api.patchNamespacedConfigMap( name, fastify.kube.namespace, diff --git a/backend/src/routes/api/components/index.ts b/backend/src/routes/api/components/index.ts index 4482a1e5ad..b1dc6abf54 100644 --- a/backend/src/routes/api/components/index.ts +++ b/backend/src/routes/api/components/index.ts @@ -1,32 +1,28 @@ -import { KubeFastifyInstance } from '../../../types'; import { FastifyReply, FastifyRequest } from 'fastify'; import { listComponents, removeComponent } from './list'; +import { KubeFastifyInstance } from '../../../types'; import { secureRoute, secureAdminRoute } from '../../../utils/route-security'; module.exports = async (fastify: KubeFastifyInstance) => { fastify.get( '/', - secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return listComponents(fastify, request) - .then((res) => { - return res; - }) + secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listComponents(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); fastify.get( '/remove', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return removeComponent(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + removeComponent(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/components/list.ts b/backend/src/routes/api/components/list.ts index e117ac3bb2..7bc1c1fd33 100644 --- a/backend/src/routes/api/components/list.ts +++ b/backend/src/routes/api/components/list.ts @@ -36,30 +36,48 @@ export const removeComponent = async ( request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { const query = request.query as { [key: string]: string }; - const coreV1Api = fastify.kube.coreV1Api; + const coreV1Api = fastify.kube?.coreV1Api; const enabledAppsConfigMapName = process.env.ENABLED_APPS_CM; - const namespace = fastify.kube.namespace; + const namespace = fastify.kube?.namespace; try { + if (typeof coreV1Api === 'undefined') { + throw new Error('coreV1Api is undefined'); + } const enabledAppsCM = await coreV1Api - .readNamespacedConfigMap(enabledAppsConfigMapName, namespace) + .readNamespacedConfigMap(enabledAppsConfigMapName ?? '', namespace ?? '') .then((result) => result.body) .catch(() => { throw new Error('Error fetching applications shown on enabled page'); }); const enabledAppsCMData = enabledAppsCM.data; - delete enabledAppsCMData[query.appName]; + if (enabledAppsCMData) { + delete enabledAppsCMData[query.appName]; + } const cmBody = { metadata: { name: enabledAppsConfigMapName, - namespace: namespace, + namespace, }, data: enabledAppsCMData, }; - await coreV1Api.replaceNamespacedConfigMap(enabledAppsConfigMapName, namespace, cmBody); + await coreV1Api.replaceNamespacedConfigMap( + enabledAppsConfigMapName ?? '', + namespace ?? '', + cmBody, + ); await updateApplications(); - return { success: true, error: null }; + return { success: true, error: '' }; } catch (e) { - fastify.log.error(e.message); - return { success: false, error: e.message }; + if ( + typeof e === 'object' && + e && + 'message' in e && + e.message && + typeof e.message === 'string' + ) { + fastify.log.error(e.message); + return { success: false, error: e.message }; + } } + throw new Error('error in removeComponent'); }; diff --git a/backend/src/routes/api/config/index.ts b/backend/src/routes/api/config/index.ts index a3ccaffcff..9d0a6ebff3 100644 --- a/backend/src/routes/api/config/index.ts +++ b/backend/src/routes/api/config/index.ts @@ -1,7 +1,7 @@ -import { KubeFastifyInstance } from '../../../types'; import { FastifyReply, FastifyRequest } from 'fastify'; -import { getDashboardConfig } from '../../../utils/resourceUtils'; import { setDashboardConfig } from './configUtils'; +import { KubeFastifyInstance } from '../../../types'; +import { getDashboardConfig } from '../../../utils/resourceUtils'; import { secureAdminRoute, secureRoute } from '../../../utils/route-security'; module.exports = async (fastify: KubeFastifyInstance) => { @@ -15,7 +15,14 @@ module.exports = async (fastify: KubeFastifyInstance) => { fastify.patch( '/', secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - reply.send(setDashboardConfig(fastify, request.body)); + if ( + typeof request === 'object' && + 'body' in request && + request.body && + typeof request.body === 'object' + ) { + reply.send(setDashboardConfig(fastify, request.body)); + } }), ); }; diff --git a/backend/src/routes/api/console-links/index.ts b/backend/src/routes/api/console-links/index.ts index 16da43e946..43639c683f 100644 --- a/backend/src/routes/api/console-links/index.ts +++ b/backend/src/routes/api/console-links/index.ts @@ -1,19 +1,17 @@ -import { KubeFastifyInstance } from '../../../types'; import { FastifyReply, FastifyRequest } from 'fastify'; import { listConsoleLinks } from './list'; +import { KubeFastifyInstance } from '../../../types'; import { secureRoute } from '../../../utils/route-security'; module.exports = async (fastify: KubeFastifyInstance) => { fastify.get( '/', - secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return listConsoleLinks() - .then((res) => { - return res; - }) + secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listConsoleLinks() + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/console-links/list.ts b/backend/src/routes/api/console-links/list.ts index 07ea0e826a..0f3670b95d 100644 --- a/backend/src/routes/api/console-links/list.ts +++ b/backend/src/routes/api/console-links/list.ts @@ -1,6 +1,5 @@ import { getConsoleLinks } from '../../../utils/resourceUtils'; import { ConsoleLinkKind } from '../../../types'; -export const listConsoleLinks = async (): Promise => { - return Promise.resolve(getConsoleLinks()); -}; +export const listConsoleLinks = async (): Promise => + Promise.resolve(getConsoleLinks()); diff --git a/backend/src/routes/api/docs/index.ts b/backend/src/routes/api/docs/index.ts index dfab7f33ff..14d261fab5 100644 --- a/backend/src/routes/api/docs/index.ts +++ b/backend/src/routes/api/docs/index.ts @@ -5,14 +5,12 @@ import { secureRoute } from '../../../utils/route-security'; export default async (fastify: FastifyInstance): Promise => { fastify.get( '/', - secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return listDocs(fastify, request) - .then((res) => { - return res; - }) + secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listDocs(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/dsc/index.ts b/backend/src/routes/api/dsc/index.ts index bae485b6db..18e70a1bfc 100644 --- a/backend/src/routes/api/dsc/index.ts +++ b/backend/src/routes/api/dsc/index.ts @@ -5,8 +5,6 @@ import { getClusterStatus } from '../../../utils/dsc'; module.exports = async (fastify: KubeFastifyInstance) => { fastify.get( '/status', - secureRoute(fastify)(async () => { - return getClusterStatus(fastify); - }), + secureRoute(fastify)(async () => getClusterStatus(fastify)), ); }; diff --git a/backend/src/routes/api/dsci/index.ts b/backend/src/routes/api/dsci/index.ts index 74bca0de5f..0a459eb7a4 100644 --- a/backend/src/routes/api/dsci/index.ts +++ b/backend/src/routes/api/dsci/index.ts @@ -5,8 +5,6 @@ import { secureRoute } from '../../../utils/route-security'; module.exports = async (fastify: KubeFastifyInstance) => { fastify.get( '/status', - secureRoute(fastify)(async () => { - return getClusterInitialization(fastify); - }), + secureRoute(fastify)(async () => getClusterInitialization(fastify)), ); }; diff --git a/backend/src/routes/api/groups-config/groupsConfigUtil.ts b/backend/src/routes/api/groups-config/groupsConfigUtil.ts index 44b20e348b..a28c3e94a4 100644 --- a/backend/src/routes/api/groups-config/groupsConfigUtil.ts +++ b/backend/src/routes/api/groups-config/groupsConfigUtil.ts @@ -1,4 +1,5 @@ import { FastifyRequest } from 'fastify'; +import createError from 'http-errors'; import { GroupsConfig, GroupsConfigBody, @@ -9,12 +10,14 @@ import { import { getAllGroups, getGroupsCR, updateGroupsCR } from '../../../utils/groupsUtils'; import { getUserName } from '../../../utils/userUtils'; import { isUserAdmin } from '../../../utils/adminUtils'; -import createError from 'http-errors'; const SYSTEM_AUTHENTICATED = 'system:authenticated'; export const getGroupsConfig = async (fastify: KubeFastifyInstance): Promise => { - const customObjectsApi = fastify.kube.customObjectsApi; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undeined'); + } + const { customObjectsApi } = fastify.kube; const groupsCluster = await getAllGroups(customObjectsApi); const groupsData = getGroupsCR(); @@ -24,15 +27,18 @@ export const getGroupsConfig = async (fastify: KubeFastifyInstance): Promise { - return groupStatus.filter((group) => group.enabled).map((group) => group.name); -}; +const transformGroupsConfig = (groupStatus: GroupStatus[]): string[] => + groupStatus.filter((group) => group.enabled).map((group) => group.name); export const updateGroupsConfig = async ( fastify: KubeFastifyInstance, request: FastifyRequest<{ Body: GroupsConfig }>, ): Promise => { - const customObjectsApi = fastify.kube.customObjectsApi; + if (typeof fastify.kube === 'undefined') { + throw new Error('fastify.kube is undeined'); + } + + const { customObjectsApi } = fastify.kube; const { namespace } = fastify.kube; const username = await getUserName(fastify, request); @@ -140,7 +146,9 @@ const getError = ( } const missingItems = array.filter(predicate); - if (missingItems.length === 0) return undefined; + if (missingItems.length === 0) { + return undefined; + } error = `The group${missingItems.length === 1 ? '' : 's'} ${missingItems.join( ', ', diff --git a/backend/src/routes/api/health/index.ts b/backend/src/routes/api/health/index.ts index dc01753f57..dc167973f1 100644 --- a/backend/src/routes/api/health/index.ts +++ b/backend/src/routes/api/health/index.ts @@ -3,7 +3,5 @@ import { KubeFastifyInstance } from '../../../types'; export default async (fastify: KubeFastifyInstance): Promise => { // Unsecured route for health check - fastify.get('/', async () => { - return health(fastify); - }); + fastify.get('/', async () => health(fastify)); }; diff --git a/backend/src/routes/api/images/index.ts b/backend/src/routes/api/images/index.ts index 96b8825935..b31227fa28 100644 --- a/backend/src/routes/api/images/index.ts +++ b/backend/src/routes/api/images/index.ts @@ -1,6 +1,6 @@ import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { ImageType } from '../../../types'; import { postImage, deleteImage, getImageList, updateImage } from './imageUtils'; +import { ImageType } from '../../../types'; import { secureAdminRoute, secureRoute } from '../../../utils/route-security'; export default async (fastify: FastifyInstance): Promise => { @@ -21,9 +21,7 @@ export default async (fastify: FastifyInstance): Promise => { }; } return getImageList(fastify, labels) - .then((res) => { - return res; - }) + .then((res) => res) .catch((res) => { reply.send(res); }); @@ -32,40 +30,34 @@ export default async (fastify: FastifyInstance): Promise => { fastify.delete( '/:image', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return deleteImage(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + deleteImage(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); fastify.put( '/:image', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return updateImage(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + updateImage(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); fastify.post( '/', - secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return postImage(fastify, request) - .then((res) => { - return res; - }) + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + postImage(fastify, request) + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/k8s/index.ts b/backend/src/routes/api/k8s/index.ts index e9fb5ea5b8..6a6ffc84f7 100644 --- a/backend/src/routes/api/k8s/index.ts +++ b/backend/src/routes/api/k8s/index.ts @@ -1,11 +1,11 @@ import { FastifyReply } from 'fastify'; -import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { PassThroughData, passThroughText, passThroughResource } from './pass-through'; +import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { logRequestDetails } from '../../../utils/fileUtils'; module.exports = async (fastify: KubeFastifyInstance) => { - const kc = fastify.kube.config; - const cluster = kc.getCurrentCluster(); + const kc = fastify.kube?.config; + const cluster = kc?.getCurrentCluster(); /** * Pass through API for all things kubernetes @@ -26,10 +26,10 @@ module.exports = async (fastify: KubeFastifyInstance) => { const data = JSON.stringify(req.body); const kubeUri = req.params['*']; - let url = `${cluster.server}/${kubeUri}`; + let url = `${cluster?.server}/${kubeUri}`; // Apply query params - const query = req.query; + const { query } = req; if (Object.keys(query).length > 0) { url += `?${Object.keys(query) .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`) diff --git a/backend/src/routes/api/namespaces/index.ts b/backend/src/routes/api/namespaces/index.ts index 77def7ba8f..efdfd5fcfd 100644 --- a/backend/src/routes/api/namespaces/index.ts +++ b/backend/src/routes/api/namespaces/index.ts @@ -1,6 +1,6 @@ -import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { applyNamespaceChange } from './namespaceUtils'; import { NamespaceApplicationCase } from './const'; +import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; import { logRequestDetails } from '../../../utils/fileUtils'; export default async (fastify: KubeFastifyInstance): Promise => { diff --git a/backend/src/routes/api/prometheus/index.ts b/backend/src/routes/api/prometheus/index.ts index 1d782eb566..b6484fda94 100644 --- a/backend/src/routes/api/prometheus/index.ts +++ b/backend/src/routes/api/prometheus/index.ts @@ -11,7 +11,7 @@ import { createCustomError } from '../../../utils/requestUtils'; import { logRequestDetails } from '../../../utils/fileUtils'; const handleError = (e: createError.HttpError) => { - if (e?.code) { + if (e.code) { throw createCustomError( 'Error with prometheus call', e.response || 'Prometheus call error', diff --git a/backend/src/routes/api/quickstarts/index.ts b/backend/src/routes/api/quickstarts/index.ts index 42e12737eb..b7c793accd 100644 --- a/backend/src/routes/api/quickstarts/index.ts +++ b/backend/src/routes/api/quickstarts/index.ts @@ -1,19 +1,17 @@ import { FastifyReply, FastifyRequest } from 'fastify'; -import { KubeFastifyInstance } from '../../../types'; import { listQuickStarts } from './list'; +import { KubeFastifyInstance } from '../../../types'; import { secureRoute } from '../../../utils/route-security'; export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', - secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - return listQuickStarts() - .then((res) => { - return res; - }) + secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listQuickStarts() + .then((res) => res) .catch((res) => { reply.send(res); - }); - }), + }), + ), ); }; diff --git a/backend/src/routes/api/quickstarts/list.ts b/backend/src/routes/api/quickstarts/list.ts index 7849766c99..716d6e8b3a 100644 --- a/backend/src/routes/api/quickstarts/list.ts +++ b/backend/src/routes/api/quickstarts/list.ts @@ -2,8 +2,7 @@ import { QuickStart } from '../../../types'; import { getQuickStarts } from '../../../utils/resourceUtils'; import { checkJupyterEnabled } from '../../../utils/componentUtils'; -export const listQuickStarts = (): Promise => { - return Promise.resolve( +export const listQuickStarts = (): Promise => + Promise.resolve( getQuickStarts().filter((qs) => checkJupyterEnabled() || qs.spec.appName !== 'jupyter'), ); -}; diff --git a/backend/src/routes/api/status/adminAllowedUsers.ts b/backend/src/routes/api/status/adminAllowedUsers.ts index e0d1873ab8..72ac539706 100644 --- a/backend/src/routes/api/status/adminAllowedUsers.ts +++ b/backend/src/routes/api/status/adminAllowedUsers.ts @@ -1,5 +1,5 @@ -import { KubeFastifyInstance } from '../../../types'; import { FastifyRequest } from 'fastify'; +import { KubeFastifyInstance } from '../../../types'; import { getUserName } from '../../../utils/userUtils'; import { getAdminUserList, @@ -21,8 +21,8 @@ const convertUserListToMap = ( userList: string[], privilege: 'Admin' | 'User', activityMap: UserActivityMap, -): AllowedUserMap => { - return userList.reduce((acc, rawUsername) => { +): AllowedUserMap => + userList.reduce((acc, rawUsername) => { let username = rawUsername; if (username.startsWith(KUBE_SAFE_PREFIX)) { // Users who start with this designation are non-k8s names @@ -34,7 +34,6 @@ const convertUserListToMap = ( [username]: { username, privilege, lastActivity: activityMap[username] ?? null }, }; }, {}); -}; const getUserActivityFromNotebook = async ( fastify: KubeFastifyInstance, @@ -44,11 +43,14 @@ const getUserActivityFromNotebook = async ( return notebooks.items .map<[string | undefined, string | undefined]>((notebook) => [ - notebook.metadata?.annotations?.['opendatahub.io/username'], - notebook.metadata?.annotations?.['notebooks.kubeflow.org/last-activity'] || - notebook.metadata?.annotations?.['kubeflow-resource-stopped'], + notebook.metadata.annotations['opendatahub.io/username'], + notebook.metadata.annotations['notebooks.kubeflow.org/last-activity'] || + notebook.metadata.annotations['kubeflow-resource-stopped'], ]) - .filter(([username, lastActivity]) => username && lastActivity) + .filter( + (arr): arr is [string, string] => + Array.isArray(arr) && typeof arr[0] === 'string' && typeof arr[1] === 'string', + ) .reduce( (acc, [username, lastActivity]) => ({ ...acc, diff --git a/backend/src/types.ts b/backend/src/types.ts index 120ee561c2..a9a011e052 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -179,7 +179,7 @@ export type SecretKind = K8sResourceCommon & { type?: string; }; -export enum BUILD_PHASE { +export enum BuildPhase { none = 'Not started', new = 'New', running = 'Running', @@ -198,7 +198,7 @@ export type BuildKind = { }; }; status: { - phase: BUILD_PHASE; + phase: BuildPhase; completionTimestamp: string; startTimestamp: string; }; @@ -382,7 +382,7 @@ export type OdhDocument = { export type BuildStatus = { name: string; - status: BUILD_PHASE; + status: BuildPhase; timestamp?: string; }; diff --git a/backend/src/utils.ts b/backend/src/utils.ts new file mode 100644 index 0000000000..be76312224 --- /dev/null +++ b/backend/src/utils.ts @@ -0,0 +1,21 @@ +export const errorHandler = (e: unknown): string => { + if (typeof e === 'object' && !!e) { + if ( + 'response' in e && + !!e.response && + typeof e.response === 'object' && + 'body' in e.response && + !!e.response.body && + typeof e.response.body === 'object' && + 'message' in e.response.body && + !!e.response.body.message && + typeof e.response.body.message === 'string' + ) { + return e.response.body.message; + } + if ('message' in e && typeof e.message === 'string') { + return e.message; + } + } + return ''; +}; diff --git a/backend/src/utils/resourceUtils.ts b/backend/src/utils/resourceUtils.ts index 3feeb60a5c..dd6f21e1ae 100644 --- a/backend/src/utils/resourceUtils.ts +++ b/backend/src/utils/resourceUtils.ts @@ -3,7 +3,7 @@ import createError from 'http-errors'; import { PatchUtils, V1ConfigMap, V1Namespace, V1NamespaceList } from '@kubernetes/client-node'; import { AcceleratorProfileKind, - BUILD_PHASE, + BuildPhase, BuildKind, BuildStatus, ConsoleLinkKind, @@ -424,7 +424,7 @@ const getBuildNumber = (build: BuildKind): number => { return !!buildNumber && parseInt(buildNumber, 10); }; -const PENDING_PHASES = [BUILD_PHASE.new, BUILD_PHASE.pending, BUILD_PHASE.cancelled]; +const PENDING_PHASES = [BuildPhase.new, BuildPhase.pending, BuildPhase.cancelled]; const compareBuilds = (b1: BuildKind, b2: BuildKind) => { const b1Pending = PENDING_PHASES.includes(b1.status.phase); @@ -465,7 +465,7 @@ const getBuildConfigStatus = ( if (bcBuilds.length === 0) { return { name: notebookName, - status: BUILD_PHASE.none, + status: BuildPhase.none, }; } const mostRecent = bcBuilds.sort(compareBuilds).pop(); @@ -479,7 +479,7 @@ const getBuildConfigStatus = ( fastify.log.error(e.response?.body?.message || e.message, 'failed to get build configs'); return { name: notebookName, - status: BUILD_PHASE.pending, + status: BuildPhase.pending, }; }); }; diff --git a/backend/src/utils/resourceWatcher.ts b/backend/src/utils/resourceWatcher.ts index dd17a355ae..e92124f18e 100644 --- a/backend/src/utils/resourceWatcher.ts +++ b/backend/src/utils/resourceWatcher.ts @@ -12,13 +12,19 @@ export type ResourceWatcherTimeUpdate = { export class ResourceWatcher { readonly fastify: KubeFastifyInstance; + readonly getter: (fastify: KubeFastifyInstance) => Promise; - readonly getTimesForResults: (results: T[]) => ResourceWatcherTimeUpdate; + + readonly getTimesForResults?: (results: T[]) => ResourceWatcherTimeUpdate; + private activeWatchInterval: number; + private inactiveWatchInterval: number; - private watchTimer: NodeJS.Timeout = undefined; - private activeTimer: NodeJS.Timeout = undefined; + private watchTimer: NodeJS.Timeout | undefined | null; + + private activeTimer: NodeJS.Timeout | undefined; + private activelyWatching = false; private resources: T[] = []; @@ -26,13 +32,15 @@ export class ResourceWatcher { constructor( fastify: KubeFastifyInstance, getter: (fastify: KubeFastifyInstance) => Promise, - getTimesForResults: (results: T[]) => ResourceWatcherTimeUpdate = undefined, + getTimesForResults?: (results: T[]) => ResourceWatcherTimeUpdate, activeWatchInterval: number = DEFAULT_ACTIVE_TIMEOUT, inactiveWatchInterval: number = DEFAULT_INACTIVE_TIMEOUT, ) { this.fastify = fastify; this.getter = getter; - this.getTimesForResults = getTimesForResults; + if (getTimesForResults) { + this.getTimesForResults = getTimesForResults; + } this.activeWatchInterval = activeWatchInterval; this.inactiveWatchInterval = inactiveWatchInterval; this.startWatching(false); @@ -46,23 +54,20 @@ export class ResourceWatcher { updateRefreshTime(): boolean { let updated = false; - if (this.getTimesForResults) { - const { activeWatchInterval, inactiveWatchInterval } = this.getTimesForResults( - this.resources, - ); - if (activeWatchInterval !== this.activeWatchInterval) { - this.activeWatchInterval = activeWatchInterval; - if (this.activelyWatching) { - updated = true; - } + const { activeWatchInterval, inactiveWatchInterval } = this.getTimesForResults(this.resources); + if (activeWatchInterval !== this.activeWatchInterval) { + this.activeWatchInterval = activeWatchInterval ?? 0; + if (this.activelyWatching) { + updated = true; } - if (inactiveWatchInterval !== this.inactiveWatchInterval) { - this.inactiveWatchInterval = inactiveWatchInterval; - if (!this.activelyWatching) { - updated = true; - } + } + if (inactiveWatchInterval !== this.inactiveWatchInterval) { + this.inactiveWatchInterval = inactiveWatchInterval ?? 0; + if (!this.activelyWatching) { + updated = true; } } + return updated; } diff --git a/backend/src/utils/route-security.ts b/backend/src/utils/route-security.ts index 5dc8476c9f..88d57cd5d5 100644 --- a/backend/src/utils/route-security.ts +++ b/backend/src/utils/route-security.ts @@ -1,3 +1,10 @@ +import { FastifyReply, FastifyRequest } from 'fastify'; +import { getOpenshiftUser, getUserName, usernameTranslate } from './userUtils'; +import { createCustomError } from './requestUtils'; +import { isUserAdmin } from './adminUtils'; +import { getNamespaces } from './notebookUtils'; +import { logRequestDetails } from './fileUtils'; +import { DEV_MODE, MODEL_REGISTRY_NAMESPACE } from './constants'; import { K8sResourceCommon, KubeFastifyInstance, @@ -5,13 +12,6 @@ import { NotebookState, OauthFastifyRequest, } from '../types'; -import { getOpenshiftUser, getUserName, usernameTranslate } from './userUtils'; -import { createCustomError } from './requestUtils'; -import { FastifyReply, FastifyRequest } from 'fastify'; -import { isUserAdmin } from './adminUtils'; -import { getNamespaces } from './notebookUtils'; -import { logRequestDetails } from './fileUtils'; -import { DEV_MODE, MODEL_REGISTRY_NAMESPACE } from './constants'; const testAdmin = async ( fastify: KubeFastifyInstance, @@ -26,7 +26,7 @@ const testAdmin = async ( return true; } - if (needsAdmin && !isAdmin) { + if (needsAdmin) { // Not an admin, route needs one -- reject fastify.log.error( `A Non-Admin User (${username}) made a request against an endpoint that requires an admin.`, @@ -86,7 +86,7 @@ const requestSecurityGuard = async ( } // Requested a resource with an explicit empty name, doesn't matter who you are, bad API request - if (name === '' || name?.trim() === '') { + if (name === '' || name.trim() === '') { throw createCustomError('404 Endpoint Not Found', 'Not Found', 404); } @@ -96,7 +96,7 @@ const requestSecurityGuard = async ( } // Api with no name object, allow reads - if (name == null && namespace === notebookNamespace && isReadRequest) { + if (!name && namespace === notebookNamespace && isReadRequest) { return; } @@ -131,23 +131,23 @@ type K8sTargetParams = { name?: string; namespace: string }; const isRequestParams = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Params: K8sTargetParams }> => - !!(request.params as K8sTargetParams)?.namespace; + !!(request.params as K8sTargetParams).namespace; const isRequestBody = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Body: K8sResourceCommon }> => - !!(request?.body as K8sResourceCommon)?.metadata?.namespace; + !!(request.body as K8sResourceCommon).metadata?.namespace; const isRequestNotebookAdmin = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Body: NotebookData }> => - !!(request?.body as NotebookData)?.username; + !!(request.body as NotebookData).username; const isRequestNotebookEndpoint = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Body: NotebookData }> => request.url === '/api/notebooks' && - Object.values(NotebookState).includes((request.body as NotebookData)?.state); + Object.values(NotebookState).includes((request.body as NotebookData).state); /** Determine which type of call it is -- request body data or request params. */ const handleSecurityOnRouteData = async ( @@ -161,23 +161,22 @@ const handleSecurityOnRouteData = async ( await requestSecurityGuard( fastify, request, - request.body.metadata.name, - request.body.metadata.namespace, + request.body.metadata?.name ?? '', + request.body.metadata?.namespace ?? '', needsAdmin, ); } else if (isRequestParams(request)) { await requestSecurityGuard( fastify, request, - request.params.name, + request.params.name ?? '', request.params.namespace, needsAdmin, ); } else if (isRequestNotebookAdmin(request)) { - await requestSecurityGuardNotebook(fastify, request, request.body.username); + await requestSecurityGuardNotebook(fastify, request, request.body.username ?? ''); } else if (isRequestNotebookEndpoint(request)) { // Endpoint has self validation internal - return; } else { // Route is un-parameterized if (await testAdmin(fastify, request, needsAdmin)) { diff --git a/backend/src/utils/userUtils.ts b/backend/src/utils/userUtils.ts index f612e572d7..25b1be7ce4 100644 --- a/backend/src/utils/userUtils.ts +++ b/backend/src/utils/userUtils.ts @@ -1,9 +1,9 @@ import { FastifyRequest } from 'fastify'; import * as _ from 'lodash'; -import { DEV_IMPERSONATE_USER, USER_ACCESS_TOKEN } from './constants'; -import { KubeFastifyInstance } from '../types'; -import { DEV_MODE } from './constants'; +import { DEV_IMPERSONATE_USER, USER_ACCESS_TOKEN, DEV_MODE } from './constants'; import { createCustomError } from './requestUtils'; +import { errorHandler } from '../utils'; +import { KubeFastifyInstance } from '../types'; import { isImpersonating } from '../devFlags'; export const usernameTranslate = (username: string): string => { @@ -40,15 +40,18 @@ export const getOpenshiftUser = async ( username: string, ): Promise => { try { - const userResponse = await fastify.kube.customObjectsApi.getClusterCustomObject( - 'user.openshift.io', - 'v1', - 'users', - username, - ); - return userResponse.body as OpenShiftUser; + if (fastify.kube) { + const userResponse = await fastify.kube.customObjectsApi.getClusterCustomObject( + 'user.openshift.io', + 'v1', + 'users', + username, + ); + return userResponse.body as OpenShiftUser; + } + throw new Error('fastify.kube is not defined.'); } catch (e) { - throw new Error(`Error retrieving user, ${e.response?.body?.message || e.message}`); + throw new Error(`Error retrieving user, ${errorHandler(e)}`); } }; @@ -66,23 +69,37 @@ export const getUser = async ( throw error; } try { - const customObjectApiNoAuth = _.cloneDeep(fastify.kube.customObjectsApi); - customObjectApiNoAuth.setApiKey(0, `Bearer ${accessToken}`); - const userResponse = await customObjectApiNoAuth.getClusterCustomObject( - 'user.openshift.io', - 'v1', - 'users', - '~', - { headers: { Authorization: `Bearer ${accessToken}` } }, - ); - return userResponse.body as OpenShiftUser; + if (fastify.kube) { + const customObjectApiNoAuth = _.cloneDeep(fastify.kube.customObjectsApi); + customObjectApiNoAuth.setApiKey(0, `Bearer ${accessToken}`); + const userResponse = await customObjectApiNoAuth.getClusterCustomObject( + 'user.openshift.io', + 'v1', + 'users', + '~', + { headers: { Authorization: `Bearer ${accessToken}` } }, + ); + return userResponse.body as OpenShiftUser; + } + throw new Error('fastify.kube not defined'); } catch (e) { - const error = createCustomError( - e.message, - `Error getting Oauth Info for user, ${e.response?.body?.message || e.message}`, - e.statusCode, - ); - throw error; + if ( + typeof e === 'object' && + e !== null && + 'message' in e && + typeof e.message === 'string' && + 'statusCode' in e && + typeof e.statusCode === 'number' + ) { + const error = createCustomError( + e.message, + `Error getting Oauth Info for user, ${errorHandler(e)}`, + e.statusCode, + ); + throw error; + } else { + throw new Error(`Error getting Oauth Info for user, ${errorHandler(e)}`); + } } }; @@ -90,24 +107,38 @@ export const getUserName = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise => { - const { currentUser } = fastify.kube; + if (fastify.kube) { + const { currentUser } = fastify.kube; - try { - const userOauth = await getUser(fastify, request); - return userOauth.metadata.name; - } catch (e) { - if (DEV_MODE) { - if (isImpersonating()) { - return DEV_IMPERSONATE_USER; + try { + const userOauth = await getUser(fastify, request); + return userOauth.metadata.name; + } catch (e) { + if (DEV_MODE) { + if (isImpersonating()) { + return DEV_IMPERSONATE_USER ?? ''; + } + return (currentUser.username || currentUser.name).split('/')[0]; + } + if ( + typeof e === 'object' && + e !== null && + 'message' in e && + typeof e.message === 'string' && + 'statusCode' in e && + typeof e.statusCode === 'number' + ) { + fastify.log.error(`Failed to retrieve username: ${errorHandler(e)}`); + const error = createCustomError( + 'Unauthorized', + `Failed to retrieve username: ${errorHandler(e)}`, + e.statusCode || 500, + ); + throw error; + } else { + throw new Error(`Error getting Oauth Info for user, ${errorHandler(e)}`); } - return (currentUser.username || currentUser.name)?.split('/')[0]; } - fastify.log.error(`Failed to retrieve username: ${e.response?.body?.message || e.message}`); - const error = createCustomError( - 'Unauthorized', - `Failed to retrieve username: ${e.response?.body?.message || e.message}`, - e.statusCode || 500, - ); - throw error; } + return ''; };