diff --git a/backend/src/plugins/kube.ts b/backend/src/plugins/kube.ts index 60610f7c61..86b2429b75 100644 --- a/backend/src/plugins/kube.ts +++ b/backend/src/plugins/kube.ts @@ -3,9 +3,9 @@ import fp from 'fastify-plugin'; import { FastifyInstance } from 'fastify'; import * as jsYaml from 'js-yaml'; import * as k8s from '@kubernetes/client-node'; +import { errorHandler, isKubeFastifyInstance } 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,7 +30,7 @@ export default fp(async (fastify: FastifyInstance) => { let currentToken; try { - currentToken = await getCurrentToken(currentUser); + currentToken = await getCurrentToken(); } catch (e) { currentToken = ''; fastify.log.error(e, 'Failed to retrieve current token'); @@ -46,25 +46,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', { @@ -83,19 +78,21 @@ export default fp(async (fastify: FastifyInstance) => { }); // Initialize the watching of resources - initializeWatchedResources(fastify); + if (isKubeFastifyInstance(fastify)) { + initializeWatchedResources(fastify); - cleanupGPU(fastify).catch((e) => - fastify.log.error( - `Unable to fully convert GPU to use accelerator profiles. ${ - e.response?.body?.message || e.message || e - }`, - ), - ); + cleanupGPU(fastify).catch((e) => + fastify.log.error( + `Unable to fully convert GPU to use accelerator profiles. ${ + e.response?.body?.message || e.message || e + }`, + ), + ); + } }); -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,10 +110,9 @@ const getCurrentNamespace = async () => { resolve(currentContext.split('/')[0]); } }); -}; -const getCurrentToken = async (currentUser: User) => { - return new Promise((resolve, reject) => { +const getCurrentToken = async () => + new Promise((resolve, reject) => { if (currentContext === 'inClusterContext') { const location = currentUser?.authProvider?.config?.tokenFile || @@ -131,4 +127,3 @@ const getCurrentToken = async (currentUser: User) => { 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..23557a6fdc 100644 --- a/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts +++ b/backend/src/routes/api/accelerator-profiles/acceleratorProfilesUtils.ts @@ -1,14 +1,15 @@ -import { KubeFastifyInstance, AcceleratorProfileKind } from '../../../types'; import { FastifyRequest } from 'fastify'; import createError from 'http-errors'; +import { errorHandler } from '../../../utils'; +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; + const { namespace } = fastify.kube; const body = request.body as AcceleratorProfileKind['spec']; const payload: AcceleratorProfileKind = { @@ -16,7 +17,7 @@ export const postAcceleratorProfile = async ( kind: 'AcceleratorProfile', metadata: { name: translateDisplayNameForK8s(body.displayName), - namespace: namespace, + namespace, annotations: { 'opendatahub.io/modified-date': new Date().toISOString(), }, @@ -36,11 +37,11 @@ export const postAcceleratorProfile = 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 (!createError.isHttpError(e) || e.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: ${errorHandler(e)}` }; } throw e; } @@ -50,8 +51,8 @@ export const deleteAcceleratorProfile = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { - const customObjectsApi = fastify.kube.customObjectsApi; - const namespace = fastify.kube.namespace; + const { customObjectsApi } = fastify.kube; + const { namespace } = fastify.kube; const params = request.params as { acceleratorProfileName: string }; try { @@ -66,11 +67,11 @@ 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 (createError.isHttpError(e) && e.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: ${errorHandler(e)}` }; } throw e; } @@ -80,8 +81,8 @@ export const updateAcceleratorProfile = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise<{ success: boolean; error: string }> => { - const customObjectsApi = fastify.kube.customObjectsApi; - const namespace = fastify.kube.namespace; + const { customObjectsApi } = fastify.kube; + const { namespace } = fastify.kube; const params = request.params as { acceleratorProfileName: string }; const body = request.body as Partial; @@ -139,11 +140,11 @@ 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 (!createError.isHttpError(e) || e.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: ${errorHandler(e)}` }; } throw e; } diff --git a/backend/src/routes/api/accelerator-profiles/index.ts b/backend/src/routes/api/accelerator-profiles/index.ts index 984b6b527a..0c268fe90c 100644 --- a/backend/src/routes/api/accelerator-profiles/index.ts +++ b/backend/src/routes/api/accelerator-profiles/index.ts @@ -1,48 +1,43 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { secureAdminRoute } from '../../../utils/route-security'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { deleteAcceleratorProfile, postAcceleratorProfile, updateAcceleratorProfile, } from './acceleratorProfilesUtils'; +import { secureAdminRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): 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..df2f38a17b 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 => getBuildStatuses(); diff --git a/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts b/backend/src/routes/api/cluster-settings/clusterSettingsUtils.ts index 44ef958369..50b6d1d960 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, isHttpError } 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'; @@ -12,7 +13,7 @@ const segmentKeyCfg = 'odh-segment-key-config'; const DEFAULT_PVC_SIZE = 20; const DEFAULT_CULLER_TIMEOUT = 31536000; // 1 year as no culling const DEFAULT_IDLENESS_CHECK_PERIOD = '1'; // 1 minute -const DEFAULT_CLUSTER_SETTINGS: ClusterSettings = { +const DEFAULT_CLUSTER_SETTINGS = { pvcSize: DEFAULT_PVC_SIZE, cullerTimeout: DEFAULT_CULLER_TIMEOUT, userTrackingEnabled: false, @@ -21,7 +22,7 @@ const DEFAULT_CLUSTER_SETTINGS: ClusterSettings = { kServe: true, modelMesh: false, }, -}; +} satisfies ClusterSettings; export const updateClusterSettings = async ( fastify: KubeFastifyInstance, @@ -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; + const { namespace } = fastify.kube; 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, + }, + }), }, }, }); @@ -82,7 +84,7 @@ export const updateClusterSettings = async ( } if (!isEnabled) { await coreV1Api.deleteNamespacedConfigMap(nbcCfg, fastify.kube.namespace).catch((e) => { - fastify.log.error('Failed to delete culler config: ') + e.message; + fastify.log.error(`Failed to delete culler config: ${e.message}`); }); } else { await patchCM(fastify, nbcCfg, { @@ -106,17 +108,17 @@ export const updateClusterSettings = async ( }; 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 }; + 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 (!isHttpError(e) || e.response.statusCode !== 404) { + return { success: false, error: `Unable to update cluster settings. ${errorHandler(e)}` }; } throw e; } @@ -126,8 +128,8 @@ export const getClusterSettings = async ( fastify: KubeFastifyInstance, request: FastifyRequest, ): Promise => { - const coreV1Api = fastify.kube.coreV1Api; - const namespace = fastify.kube.namespace; + const { coreV1Api } = fastify.kube; + const { namespace } = fastify.kube; const dashConfig = getDashboardConfig(request); const isJupyterEnabled = checkJupyterEnabled(); const clusterSettings: ClusterSettings = { @@ -146,7 +148,7 @@ export const getClusterSettings = async ( 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.'); } @@ -154,18 +156,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; } diff --git a/backend/src/routes/api/cluster-settings/index.ts b/backend/src/routes/api/cluster-settings/index.ts index b7a7832dca..de9f5cbd81 100644 --- a/backend/src/routes/api/cluster-settings/index.ts +++ b/backend/src/routes/api/cluster-settings/index.ts @@ -1,9 +1,9 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { getClusterSettings, updateClusterSettings } from './clusterSettingsUtils'; -import { ClusterSettings } from '../../../types'; +import { ClusterSettings, KubeFastifyInstance } from '../../../types'; import { secureAdminRoute } from '../../../utils/route-security'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { 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..d208617a7f 100644 --- a/backend/src/routes/api/components/list.ts +++ b/backend/src/routes/api/components/list.ts @@ -36,30 +36,33 @@ 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; const enabledAppsConfigMapName = process.env.ENABLED_APPS_CM; - const namespace = fastify.kube.namespace; + const { namespace } = fastify.kube; try { 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 }; + const message = e instanceof Error ? e.message : 'Error removing component.'; + fastify.log.error(message); + return { success: false, error: message }; } }; diff --git a/backend/src/routes/api/config/index.ts b/backend/src/routes/api/config/index.ts index a3ccaffcff..468a8ad6ba 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,9 @@ module.exports = async (fastify: KubeFastifyInstance) => { fastify.patch( '/', secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { - reply.send(setDashboardConfig(fastify, request.body)); + if ('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..ebf2697cd0 100644 --- a/backend/src/routes/api/docs/index.ts +++ b/backend/src/routes/api/docs/index.ts @@ -1,18 +1,17 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { listDocs } from './list'; import { secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): 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/envs/index.ts b/backend/src/routes/api/envs/index.ts index 596d8c3da1..1e94d7b7b4 100644 --- a/backend/src/routes/api/envs/index.ts +++ b/backend/src/routes/api/envs/index.ts @@ -1,8 +1,9 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { getConfigMap, getSecret } from '../../../utils/envUtils'; import { secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/secret/:namespace/:name', secureRoute(fastify)( diff --git a/backend/src/routes/api/groups-config/groupsConfigUtil.ts b/backend/src/routes/api/groups-config/groupsConfigUtil.ts index 44b20e348b..ee504b4bd8 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,11 @@ 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; + const { customObjectsApi } = fastify.kube; const groupsCluster = await getAllGroups(customObjectsApi); const groupsData = getGroupsCR(); @@ -24,15 +24,14 @@ 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; + const { customObjectsApi } = fastify.kube; const { namespace } = fastify.kube; const username = await getUserName(fastify, request); @@ -140,7 +139,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/groups-config/index.ts b/backend/src/routes/api/groups-config/index.ts index 9b70b5e59e..551b249eac 100644 --- a/backend/src/routes/api/groups-config/index.ts +++ b/backend/src/routes/api/groups-config/index.ts @@ -1,9 +1,9 @@ -import { FastifyInstance, FastifyRequest } from 'fastify'; -import { GroupsConfig } from '../../../types'; +import { FastifyRequest } from 'fastify'; +import { GroupsConfig, KubeFastifyInstance } from '../../../types'; import { getGroupsConfig, updateGroupsConfig } from './groupsConfigUtil'; import { secureAdminRoute } from '../../../utils/route-security'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', secureAdminRoute(fastify)(async (request, reply) => { 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..9a4fb6c360 100644 --- a/backend/src/routes/api/images/index.ts +++ b/backend/src/routes/api/images/index.ts @@ -1,9 +1,9 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { ImageType } from '../../../types'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { postImage, deleteImage, getImageList, updateImage } from './imageUtils'; +import { ImageType, KubeFastifyInstance } from '../../../types'; import { secureAdminRoute, secureRoute } from '../../../utils/route-security'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/:type', secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { @@ -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..21d63cf24e 100644 --- a/backend/src/routes/api/k8s/index.ts +++ b/backend/src/routes/api/k8s/index.ts @@ -1,6 +1,6 @@ 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) => { @@ -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/nb-events/index.ts b/backend/src/routes/api/nb-events/index.ts index 7c3b2bdc6a..ad170daf76 100644 --- a/backend/src/routes/api/nb-events/index.ts +++ b/backend/src/routes/api/nb-events/index.ts @@ -1,8 +1,9 @@ -import { FastifyInstance, FastifyRequest } from 'fastify'; +import { FastifyRequest } from 'fastify'; import { getNotebookEvents } from './eventUtils'; import { secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { const routeHandler = secureRoute(fastify)( async ( request: FastifyRequest<{ diff --git a/backend/src/routes/api/notebooks/utils.ts b/backend/src/routes/api/notebooks/utils.ts index 2dc66512ab..d1bb712cf0 100644 --- a/backend/src/routes/api/notebooks/utils.ts +++ b/backend/src/routes/api/notebooks/utils.ts @@ -1,5 +1,7 @@ -import { KubeFastifyInstance, Notebook, NotebookData } from '../../../types'; import { V1ContainerStatus, V1Pod, V1PodList } from '@kubernetes/client-node'; +import { FastifyRequest } from 'fastify'; +import { isHttpError } from '../../../utils'; +import { KubeFastifyInstance, Notebook, NotebookData } from '../../../types'; import { getUserName } from '../../../utils/userUtils'; import { createNotebook, @@ -9,7 +11,6 @@ import { getRoute, updateNotebook, } from '../../../utils/notebookUtils'; -import { FastifyRequest } from 'fastify'; export const getNotebookStatus = async ( fastify: KubeFastifyInstance, @@ -17,7 +18,7 @@ export const getNotebookStatus = async ( name: string, ): Promise<{ notebook: Notebook; isRunning: boolean; podUID: string; notebookLink: string }> => { const notebook = await getNotebook(fastify, namespace, name); - const hasStopAnnotation = !!notebook?.metadata.annotations?.['kubeflow-resource-stopped']; + const hasStopAnnotation = !!notebook.metadata.annotations?.['kubeflow-resource-stopped']; const [isRunning, podUID] = hasStopAnnotation ? [false, ''] : await listNotebookStatus(fastify, namespace, name); @@ -47,7 +48,7 @@ export const listNotebookStatus = async ( `notebook-name=${name}`, ); const pods = (response.body as V1PodList).items; - return [pods.some((pod) => checkPodContainersReady(pod)), pods[0]?.metadata.uid || '']; + return [pods.some((pod) => checkPodContainersReady(pod)), pods[0]?.metadata?.uid || '']; }; export const checkPodContainersReady = (pod: V1Pod): boolean => { @@ -74,12 +75,11 @@ export const enableNotebook = async ( try { const notebook = await getNotebook(fastify, notebookNamespace, name); - return await updateNotebook(fastify, username, url, notebookData, notebook); + return await updateNotebook(fastify, username, url ?? '', notebookData, notebook); } catch (e) { - if (e.response?.statusCode === 404) { - return await createNotebook(fastify, username, url, notebookData); - } else { - throw e; + if (isHttpError(e) && e.response.statusCode === 404) { + return await createNotebook(fastify, username, url ?? '', notebookData); } + throw e; } }; diff --git a/backend/src/routes/api/prometheus/index.ts b/backend/src/routes/api/prometheus/index.ts index 1d782eb566..ae2b66f507 100644 --- a/backend/src/routes/api/prometheus/index.ts +++ b/backend/src/routes/api/prometheus/index.ts @@ -10,7 +10,7 @@ import { callPrometheusThanos } from '../../../utils/prometheusUtils'; import { createCustomError } from '../../../utils/requestUtils'; import { logRequestDetails } from '../../../utils/fileUtils'; -const handleError = (e: createError.HttpError) => { +const handleError = (e?: createError.HttpError) => { if (e?.code) { throw createCustomError( 'Error with prometheus call', 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/segment-key/index.ts b/backend/src/routes/api/segment-key/index.ts index 5426847bdc..8359175b3d 100644 --- a/backend/src/routes/api/segment-key/index.ts +++ b/backend/src/routes/api/segment-key/index.ts @@ -1,8 +1,9 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { getSegmentKey } from './segmentKeyUtils'; import { secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { diff --git a/backend/src/routes/api/status/adminAllowedUsers.ts b/backend/src/routes/api/status/adminAllowedUsers.ts index e0d1873ab8..3ca0ffffad 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/routes/api/status/index.ts b/backend/src/routes/api/status/index.ts index ee146b49bf..e99115487f 100644 --- a/backend/src/routes/api/status/index.ts +++ b/backend/src/routes/api/status/index.ts @@ -1,9 +1,10 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { status } from './statusUtils'; import { getAllowedUsers } from './adminAllowedUsers'; import { secureAdminRoute, secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { diff --git a/backend/src/routes/api/status/statusUtils.ts b/backend/src/routes/api/status/statusUtils.ts index 295890db85..c5d110fdc4 100644 --- a/backend/src/routes/api/status/statusUtils.ts +++ b/backend/src/routes/api/status/statusUtils.ts @@ -12,7 +12,12 @@ export const status = async ( const kubeContext = fastify.kube.currentContext; const { config, currentContext, namespace, currentUser, clusterID, clusterBranding } = fastify.kube; - const { server } = config.getCurrentCluster(); + const currentCluster = config.getCurrentCluster(); + if (currentCluster === null) { + throw new Error('The current cluster cannot be null'); + } + + const { server } = currentCluster; const userName = await getUserName(fastify, request); const isAdmin = await isUserAdmin(fastify, userName, namespace); diff --git a/backend/src/routes/api/storage/index.ts b/backend/src/routes/api/storage/index.ts index 4a93fa3894..5cec68b016 100644 --- a/backend/src/routes/api/storage/index.ts +++ b/backend/src/routes/api/storage/index.ts @@ -1,9 +1,9 @@ -import { FastifyInstance, FastifyReply } from 'fastify'; +import { FastifyReply } from 'fastify'; import { getObjectSize, getObjectStream, setupMinioClient } from './storageUtils'; import { getDashboardConfig } from '../../../utils/resourceUtils'; -import { OauthFastifyRequest } from '../../../types'; +import { KubeFastifyInstance, OauthFastifyRequest } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/:namespace/size', async ( diff --git a/backend/src/routes/api/validate-isv/index.ts b/backend/src/routes/api/validate-isv/index.ts index badacbfbdb..4f8e2c1698 100644 --- a/backend/src/routes/api/validate-isv/index.ts +++ b/backend/src/routes/api/validate-isv/index.ts @@ -1,8 +1,9 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from 'fastify'; import { getValidateISVResults, validateISV } from './validateISV'; import { secureRoute } from '../../../utils/route-security'; +import { KubeFastifyInstance } from '../../../types'; -export default async (fastify: FastifyInstance): Promise => { +export default async (fastify: KubeFastifyInstance): Promise => { fastify.get( '/', secureRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => { diff --git a/backend/src/types.ts b/backend/src/types.ts index 120ee561c2..c786fa65b0 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -148,6 +148,12 @@ export type K8sResourceCommon = { }; } & K8sResourceBase; +export type K8sNamespacedResourceCommon = { + metadata: { + namespace: string; + }; +} & K8sResourceCommon; + export type K8sResourceListResult = { apiVersion: string; items: TResource[]; @@ -179,7 +185,7 @@ export type SecretKind = K8sResourceCommon & { type?: string; }; -export enum BUILD_PHASE { +export enum BuildPhase { none = 'Not started', new = 'New', running = 'Running', @@ -198,7 +204,7 @@ export type BuildKind = { }; }; status: { - phase: BUILD_PHASE; + phase: BuildPhase; completionTimestamp: string; startTimestamp: string; }; @@ -285,7 +291,7 @@ export type KubeDecorator = KubeStatus & { }; export type KubeFastifyInstance = FastifyInstance & { - kube?: KubeDecorator; + kube: KubeDecorator; }; // TODO: constant-ize the x-forwarded header @@ -382,7 +388,7 @@ export type OdhDocument = { export type BuildStatus = { name: string; - status: BUILD_PHASE; + status: BuildPhase; timestamp?: string; }; @@ -427,7 +433,7 @@ export type Volume = { export type Notebook = K8sResourceCommon & { metadata: { - annotations: Partial<{ + annotations?: Partial<{ 'kubeflow-resource-stopped': string; // datestamp of stop (if omitted, it is running) 'notebooks.kubeflow.org/last-activity': string; // datestamp of last use 'opendatahub.io/username': string; // the untranslated username behind the notebook diff --git a/backend/src/utils.ts b/backend/src/utils.ts new file mode 100644 index 0000000000..0d1dae4a76 --- /dev/null +++ b/backend/src/utils.ts @@ -0,0 +1,30 @@ +import { FastifyInstance } from 'fastify'; +import { HttpError } from '@kubernetes/client-node'; +import { KubeFastifyInstance } from './types'; + +export const isHttpError = (e: unknown): e is HttpError => e instanceof HttpError; + +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 (e instanceof Error) { + return e.message; + } + } + return ''; +}; + +export const isKubeFastifyInstance = (obj: FastifyInstance): obj is KubeFastifyInstance => + 'kube' in obj; diff --git a/backend/src/utils/resourceUtils.ts b/backend/src/utils/resourceUtils.ts index dd7465d001..8d1a94b629 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, @@ -425,7 +425,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); @@ -466,7 +466,7 @@ const getBuildConfigStatus = ( if (bcBuilds.length === 0) { return { name: notebookName, - status: BUILD_PHASE.none, + status: BuildPhase.none, }; } const mostRecent = bcBuilds.sort(compareBuilds).pop(); @@ -480,7 +480,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..e840dd8664 100644 --- a/backend/src/utils/resourceWatcher.ts +++ b/backend/src/utils/resourceWatcher.ts @@ -6,19 +6,25 @@ export const DEFAULT_INACTIVE_TIMEOUT: number = 30 * 60 * 1000; export const ACTIVITY_TIMEOUT: number = 2 * 60 * 1000; export type ResourceWatcherTimeUpdate = { - activeWatchInterval?: number; - inactiveWatchInterval?: number; + activeWatchInterval: number; + inactiveWatchInterval: number; }; 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,7 +32,7 @@ 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, ) { diff --git a/backend/src/utils/route-security.ts b/backend/src/utils/route-security.ts index 5dc8476c9f..149518472a 100644 --- a/backend/src/utils/route-security.ts +++ b/backend/src/utils/route-security.ts @@ -1,17 +1,17 @@ -import { - K8sResourceCommon, - KubeFastifyInstance, - NotebookData, - NotebookState, - OauthFastifyRequest, -} from '../types'; +import { FastifyReply, FastifyRequest } from 'fastify'; 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'; +import { + K8sNamespacedResourceCommon, + KubeFastifyInstance, + NotebookData, + NotebookState, + OauthFastifyRequest, +} from '../types'; 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.`, @@ -63,9 +63,9 @@ const requestSecurityGuardNotebook = async ( const requestSecurityGuard = async ( fastify: KubeFastifyInstance, request: OauthFastifyRequest, - name: string, namespace: string, needsAdmin: boolean, + name?: string, ): Promise => { const { notebookNamespace, dashboardNamespace } = getNamespaces(fastify); const username = await getUserName(fastify, request); @@ -131,23 +131,41 @@ type K8sTargetParams = { name?: string; namespace: string }; const isRequestParams = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Params: K8sTargetParams }> => - !!(request.params as K8sTargetParams)?.namespace; + 'params' in request && + !!request.params && + typeof request.params === 'object' && + 'namespace' in request.params && + !!request.params.namespace; const isRequestBody = ( request: FastifyRequest, -): request is OauthFastifyRequest<{ Body: K8sResourceCommon }> => - !!(request?.body as K8sResourceCommon)?.metadata?.namespace; +): request is OauthFastifyRequest<{ Body: K8sNamespacedResourceCommon }> => + 'body' in request && + !!request.body && + typeof request.body === 'object' && + 'metadata' in request.body && + !!request.body.metadata && + typeof request.body.metadata === 'object' && + 'namespace' in request.body.metadata && + !!request.body.metadata.namespace && + typeof request.body.metadata.namespace !== 'undefined'; + +const isNotebookData = (obj: unknown): obj is NotebookData => + typeof obj === 'object' && !!obj && ('username' in obj || 'state' in obj); const isRequestNotebookAdmin = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Body: NotebookData }> => - !!(request?.body as NotebookData)?.username; + 'body' in request && !!request.body && isNotebookData(request.body) && !!request.body.username; const isRequestNotebookEndpoint = ( request: FastifyRequest, ): request is OauthFastifyRequest<{ Body: NotebookData }> => request.url === '/api/notebooks' && - Object.values(NotebookState).includes((request.body as NotebookData)?.state); + 'body' in request && + !!request.body && + isNotebookData(request.body) && + Object.values(NotebookState).includes(request.body.state); /** Determine which type of call it is -- request body data or request params. */ const handleSecurityOnRouteData = async ( @@ -161,23 +179,22 @@ const handleSecurityOnRouteData = async ( await requestSecurityGuard( fastify, request, - request.body.metadata.name, request.body.metadata.namespace, needsAdmin, + request.body.metadata.name, ); } else if (isRequestParams(request)) { await requestSecurityGuard( fastify, request, - request.params.name, request.params.namespace, needsAdmin, + request.params.name, ); } 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..ade0f44fbb 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, isHttpError } from '../utils'; +import { KubeFastifyInstance } from '../types'; import { isImpersonating } from '../devFlags'; export const usernameTranslate = (username: string): string => { @@ -48,7 +48,7 @@ export const getOpenshiftUser = async ( ); return userResponse.body as OpenShiftUser; } catch (e) { - throw new Error(`Error retrieving user, ${e.response?.body?.message || e.message}`); + throw new Error(`Error retrieving user, ${errorHandler(e)}`); } }; @@ -78,9 +78,9 @@ export const getUser = async ( return userResponse.body as OpenShiftUser; } catch (e) { const error = createCustomError( - e.message, - `Error getting Oauth Info for user, ${e.response?.body?.message || e.message}`, - e.statusCode, + errorHandler(e), + `Error getting Oauth Info for user, ${errorHandler(e)}`, + (isHttpError(e) && e.statusCode) || 401, ); throw error; } @@ -98,15 +98,15 @@ export const getUserName = async ( } catch (e) { if (DEV_MODE) { if (isImpersonating()) { - return DEV_IMPERSONATE_USER; + return DEV_IMPERSONATE_USER ?? ''; } - return (currentUser.username || currentUser.name)?.split('/')[0]; + return (currentUser.username || currentUser.name).split('/')[0]; } - fastify.log.error(`Failed to retrieve username: ${e.response?.body?.message || e.message}`); + fastify.log.error(`Failed to retrieve username: ${errorHandler(e)}`); const error = createCustomError( 'Unauthorized', - `Failed to retrieve username: ${e.response?.body?.message || e.message}`, - e.statusCode || 500, + `Failed to retrieve username: ${errorHandler(e)}`, + (isHttpError(e) && e.statusCode) || 500, ); throw error; }