From 4b763274890e44142c36cd01ee4990abde380d6c Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Fri, 11 Feb 2022 15:06:34 +0200 Subject: [PATCH] Introduce healthy abstraction between back-end routes and route handlers Signed-off-by: Janne Savolainen --- src/main/__test__/router.test.ts | 4 +- src/main/router.ts | 142 +++++++++++++++++- .../helm/charts/get-chart-route.injectable.ts | 24 ++- .../get-chart-values-route.injectable.ts | 24 ++- .../charts/list-charts-route.injectable.ts | 13 +- .../delete-release-route.injectable.ts | 23 ++- .../get-release-history-route.injectable.ts | 23 ++- .../releases/get-release-route.injectable.ts | 23 ++- .../get-release-values-route.injectable.ts | 24 +-- .../install-chart-route.injectable.ts | 20 +-- .../list-releases-route.injectable.ts | 19 +-- .../rollback-release-route.injectable.ts | 16 +- .../update-release-route.injectable.ts | 28 ++-- .../get-service-account-route.injectable.ts | 12 +- .../metrics/add-metrics-route.injectable.ts | 39 ++--- .../get-metric-providers-route.injectable.ts | 8 +- ...t-current-port-forward-route.injectable.ts | 9 +- .../start-port-forward-route.injectable.ts | 25 ++- ...p-current-port-forward-route.injectable.ts | 19 ++- .../apply-resource-route.injectable.ts | 20 +-- .../patch-resource-route.injectable.ts | 25 ++- .../routes/static-file-route.injectable.ts | 54 +++---- .../versions/get-version-route.injectable.ts | 10 +- 23 files changed, 338 insertions(+), 266 deletions(-) diff --git a/src/main/__test__/router.test.ts b/src/main/__test__/router.test.ts index 2edd10cc81..4d244ac6f4 100644 --- a/src/main/__test__/router.test.ts +++ b/src/main/__test__/router.test.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { LensApiRequest } from "../router"; +import type { LensApiRequest, LensApiResult } from "../router"; import staticFileRouteInjectable from "../routes/static-file-route.injectable"; import { getDiForUnitTesting } from "../getDiForUnitTesting"; @@ -24,7 +24,7 @@ jest.mock("electron", () => ({ })); describe("Router", () => { - let handleStaticFile: (request: LensApiRequest) => Promise; + let handleStaticFile: (request: LensApiRequest) => Promise; beforeEach(async () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); diff --git a/src/main/router.ts b/src/main/router.ts index 47e457b2b0..67c2474092 100644 --- a/src/main/router.ts +++ b/src/main/router.ts @@ -6,6 +6,8 @@ import Call from "@hapi/call"; import Subtext from "@hapi/subtext"; import type http from "http"; +import type httpProxy from "http-proxy"; +import { toPairs } from "lodash/fp"; import path from "path"; import type { Cluster } from "../common/cluster/cluster"; @@ -40,6 +42,94 @@ export interface LensApiRequest

{ }; } +const respondFor = + (contentType: string) => + ( + content: any, + statusCode: number, + response: http.ServerResponse, + ) => { + response.statusCode = statusCode; + response.setHeader("Content-Type", contentType); + + if (content instanceof Buffer) { + response.write(content); + + response.end(); + + return; + } + + response.end(content); + }; + +export type SupportedFileExtension = "json" | "txt" | "html" | "css" | "gif" | "jpg" | "png" | "svg" | "js" | "woff2" | "ttf"; + +export const contentTypes: Record = { + json: { + respond: ( + content: any, + statusCode: number, + response: http.ServerResponse, + ) => { + response.statusCode = statusCode; + response.setHeader("Content-Type", "application/json"); + + if (content instanceof Buffer) { + response.write(content); + response.end(); + + return; + } + + const normalizedContent = + typeof content === "object" ? JSON.stringify(content) : content; + + response.end(normalizedContent); + }, + }, + + txt: { + respond: respondFor("text/plain"), + }, + + html: { + respond: respondFor("text/html"), + }, + + css: { + respond: respondFor("text/css"), + }, + + gif: { + respond: respondFor("image/gif"), + }, + + jpg: { + respond: respondFor("image/jpeg"), + }, + + png: { + respond: respondFor("image/png"), + }, + + svg: { + respond: respondFor("image/svg+xml"), + }, + + js: { + respond: respondFor("application/javascript"), + }, + + woff2: { + respond: respondFor("font/woff2"), + }, + + ttf: { + respond: respondFor("font/ttf"), + }, +}; + export class Router { protected router = new Call.Router(); static rootPath = path.resolve(__static); @@ -47,7 +137,42 @@ export class Router { constructor(routes: Route[]) { routes.forEach(route => { this.router.add({ method: route.method, path: route.path }, async (request: LensApiRequest) => { - await route.handler(request); + let result: LensApiResult | void; + + try { + result = await route.handler(request); + } catch(error) { + contentTypes.txt.respond(error.toString(), 422, request.response); + + return; + } + + if (!result) { + contentTypes.txt.respond(null, 204, request.response); + + return; + } + + const { + response, + error, + statusCode = error ? 400 : 200, + contentType = contentTypes.json, + headers = {}, + proxy, + } = result; + + if (proxy) { + return; + } + + const headerNameValuePairs = toPairs(headers); + + headerNameValuePairs.forEach(([key, value]) => { + request.response.setHeader(key, value); + }); + + contentType.respond(error || response, statusCode, request.response); }); }); } @@ -91,8 +216,21 @@ export class Router { } } +interface LensApiResultContentType { + respond: (content: any, statusCode: number, response: http.ServerResponse) => void; +} + +export interface LensApiResult { + statusCode?: number; + response?: any; + error?: any; + contentType?: LensApiResultContentType; + headers?: { Location: string }; + proxy?: httpProxy; +} + export interface Route { path: string; method: "get" | "post" | "put" | "patch" | "delete"; - handler: (request: LensApiRequest) => void | Promise; + handler: (request: LensApiRequest) => LensApiResult | Promise; } diff --git a/src/main/routes/helm/charts/get-chart-route.injectable.ts b/src/main/routes/helm/charts/get-chart-route.injectable.ts index 57203c895c..4f2e94e5d1 100644 --- a/src/main/routes/helm/charts/get-chart-route.injectable.ts +++ b/src/main/routes/helm/charts/get-chart-route.injectable.ts @@ -4,9 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../../router/router.injectable"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { apiPrefix } from "../../../../common/vars"; const getChartRouteInjectable = getInjectable({ @@ -16,17 +15,16 @@ const getChartRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}`, - handler: async (request: LensApiRequest) => { - const { params, query, response } = request; - - try { - const chart = await helmService.getChart(params.repo, params.chart, query.get("version")); - - respondJson(response, chart); - } catch (error) { - respondText(response, error?.toString() || "Error getting chart", 422); - } - }, + handler: async ({ + params, + query, + }: LensApiRequest): Promise => ({ + response: await helmService.getChart( + params.repo, + params.chart, + query.get("version"), + ), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/helm/charts/get-chart-values-route.injectable.ts b/src/main/routes/helm/charts/get-chart-values-route.injectable.ts index c446edabbc..58510e2f86 100644 --- a/src/main/routes/helm/charts/get-chart-values-route.injectable.ts +++ b/src/main/routes/helm/charts/get-chart-values-route.injectable.ts @@ -4,9 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../../router/router.injectable"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { apiPrefix } from "../../../../common/vars"; const getChartRouteValuesInjectable = getInjectable({ @@ -16,17 +15,16 @@ const getChartRouteValuesInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values`, - handler: async (request: LensApiRequest) => { - const { params, query, response } = request; - - try { - const values = await helmService.getChartValues(params.repo, params.chart, query.get("version")); - - respondJson(response, values); - } catch (error) { - respondText(response, error?.toString() || "Error getting chart values", 422); - } - }, + handler: async ({ + params, + query, + }: LensApiRequest): Promise => ({ + response: await helmService.getChartValues( + params.repo, + params.chart, + query.get("version"), + ), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/helm/charts/list-charts-route.injectable.ts b/src/main/routes/helm/charts/list-charts-route.injectable.ts index 51ddcde94e..7ebecba499 100644 --- a/src/main/routes/helm/charts/list-charts-route.injectable.ts +++ b/src/main/routes/helm/charts/list-charts-route.injectable.ts @@ -4,9 +4,8 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../../router/router.injectable"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson } from "../../../utils/http-responses"; import { apiPrefix } from "../../../../common/vars"; const listChartsRouteInjectable = getInjectable({ @@ -16,12 +15,10 @@ const listChartsRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/charts`, - handler: async (request: LensApiRequest) => { - const { response } = request; - const charts = await helmService.listCharts(); - - respondJson(response, charts); - }, + // eslint-disable-next-line unused-imports/no-unused-vars-ts + handler: async (request: LensApiRequest): Promise => ({ + response: await helmService.listCharts(), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/helm/releases/delete-release-route.injectable.ts b/src/main/routes/helm/releases/delete-release-route.injectable.ts index 0039bdb5f9..c7a223822e 100644 --- a/src/main/routes/helm/releases/delete-release-route.injectable.ts +++ b/src/main/routes/helm/releases/delete-release-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const deleteReleaseRouteInjectable = getInjectable({ id: "delete-release-route", @@ -17,17 +15,16 @@ const deleteReleaseRouteInjectable = getInjectable({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}`, - handler: async (request: LensApiRequest) => { - const { cluster, params, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params } = request; - try { - const result = await helmService.deleteRelease(cluster, params.release, params.namespace); - - respondJson(response, result); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error deleting release", 422); - } + return { + response: await helmService.deleteRelease( + cluster, + params.release, + params.namespace, + ), + }; }, }), diff --git a/src/main/routes/helm/releases/get-release-history-route.injectable.ts b/src/main/routes/helm/releases/get-release-history-route.injectable.ts index 2ea5f01570..659418819f 100644 --- a/src/main/routes/helm/releases/get-release-history-route.injectable.ts +++ b/src/main/routes/helm/releases/get-release-history-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const getReleaseRouteHistoryInjectable = getInjectable({ id: "get-release-history-route", @@ -17,17 +15,16 @@ const getReleaseRouteHistoryInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history`, - handler: async (request: LensApiRequest) => { - const { cluster, params, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params } = request; - try { - const result = await helmService.getReleaseHistory(cluster, params.release, params.namespace); - - respondJson(response, result); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error getting release history", 422); - } + return { + response: await helmService.getReleaseHistory( + cluster, + params.release, + params.namespace, + ), + }; }, }), diff --git a/src/main/routes/helm/releases/get-release-route.injectable.ts b/src/main/routes/helm/releases/get-release-route.injectable.ts index b92d4756ab..185a1e0368 100644 --- a/src/main/routes/helm/releases/get-release-route.injectable.ts +++ b/src/main/routes/helm/releases/get-release-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const getReleaseRouteInjectable = getInjectable({ id: "get-release-route", @@ -17,17 +15,16 @@ const getReleaseRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}`, - handler: async (request: LensApiRequest) => { - const { cluster, params, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params } = request; - try { - const result = await helmService.getRelease(cluster, params.release, params.namespace); - - respondJson(response, result); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error getting release", 422); - } + return { + response: await helmService.getRelease( + cluster, + params.release, + params.namespace, + ), + }; }, }), diff --git a/src/main/routes/helm/releases/get-release-values-route.injectable.ts b/src/main/routes/helm/releases/get-release-values-route.injectable.ts index 9cd38b0153..3c99989cb7 100644 --- a/src/main/routes/helm/releases/get-release-values-route.injectable.ts +++ b/src/main/routes/helm/releases/get-release-values-route.injectable.ts @@ -3,13 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; import { getBoolean } from "../../../utils/parse-query"; +import { contentTypes } from "../../../router"; const getReleaseRouteValuesInjectable = getInjectable({ id: "get-release-values-route", @@ -18,18 +17,19 @@ const getReleaseRouteValuesInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values`, - handler: async (request: LensApiRequest) => { - const { cluster, params: { namespace, release }, response, query } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params: { namespace, release }, query } = request; const all = getBoolean(query, "all"); - try { - const result = await helmService.getReleaseValues(release, { cluster, namespace, all }); + return { + response: await helmService.getReleaseValues(release, { + cluster, + namespace, + all, + }), - respondText(response, result); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error getting release values", 422); - } + contentType: contentTypes.txt, + }; }, }), diff --git a/src/main/routes/helm/releases/install-chart-route.injectable.ts b/src/main/routes/helm/releases/install-chart-route.injectable.ts index 6de206dbf7..28b79171e2 100644 --- a/src/main/routes/helm/releases/install-chart-route.injectable.ts +++ b/src/main/routes/helm/releases/install-chart-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const installChartRouteInjectable = getInjectable({ id: "install-chart-route", @@ -17,17 +15,13 @@ const installChartRouteInjectable = getInjectable({ method: "post", path: `${apiPrefix}/v2/releases`, - handler: async (request: LensApiRequest) => { - const { payload, cluster, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { payload, cluster } = request; - try { - const result = await helmService.installChart(cluster, payload); - - respondJson(response, result, 201); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error installing chart", 422); - } + return { + response: await helmService.installChart(cluster, payload), + statusCode: 201, + }; }, }), diff --git a/src/main/routes/helm/releases/list-releases-route.injectable.ts b/src/main/routes/helm/releases/list-releases-route.injectable.ts index c7abc23797..ac8ee79242 100644 --- a/src/main/routes/helm/releases/list-releases-route.injectable.ts +++ b/src/main/routes/helm/releases/list-releases-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const listReleasesRouteInjectable = getInjectable({ id: "list-releases-route", @@ -17,17 +15,12 @@ const listReleasesRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}`, - handler: async (request: LensApiRequest) => { - const { cluster, params, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params } = request; - try { - const result = await helmService.listReleases(cluster, params.namespace); - - respondJson(response, result); - } catch(error) { - logger.debug(error); - respondText(response, error?.toString() || "Error listing release", 422); - } + return { + response: await helmService.listReleases(cluster, params.namespace), + }; }, }), diff --git a/src/main/routes/helm/releases/rollback-release-route.injectable.ts b/src/main/routes/helm/releases/rollback-release-route.injectable.ts index bff42c3f9b..ec50eedd19 100644 --- a/src/main/routes/helm/releases/rollback-release-route.injectable.ts +++ b/src/main/routes/helm/releases/rollback-release-route.injectable.ts @@ -5,10 +5,8 @@ import { apiPrefix } from "../../../../common/vars"; import type { LensApiRequest } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const rollbackReleaseRouteInjectable = getInjectable({ id: "rollback-release-route", @@ -17,20 +15,14 @@ const rollbackReleaseRouteInjectable = getInjectable({ method: "put", path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback`, - handler: async (request: LensApiRequest) => { - const { cluster, params, payload, response } = request; + handler: async (request: LensApiRequest): Promise => { + const { cluster, params, payload } = request; - try { - await helmService.rollback(cluster, params.release, params.namespace, payload.revision); - - response.end(); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error rolling back chart", 422); - } + await helmService.rollback(cluster, params.release, params.namespace, payload.revision); }, }), + // @ts-ignore injectionToken: routeInjectionToken, }); diff --git a/src/main/routes/helm/releases/update-release-route.injectable.ts b/src/main/routes/helm/releases/update-release-route.injectable.ts index 684e944de9..a628db6303 100644 --- a/src/main/routes/helm/releases/update-release-route.injectable.ts +++ b/src/main/routes/helm/releases/update-release-route.injectable.ts @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import type { LensApiRequest } from "../../../router"; +import type { LensApiRequest, LensApiResult } from "../../../router"; import { helmService } from "../../../helm/helm-service"; -import { respondJson, respondText } from "../../../utils/http-responses"; import { routeInjectionToken } from "../../../router/router.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import logger from "../../../logger"; const updateReleaseRouteInjectable = getInjectable({ id: "update-release-route", @@ -17,18 +15,18 @@ const updateReleaseRouteInjectable = getInjectable({ method: "put", path: `${apiPrefix}/v2/releases/{namespace}/{release}`, - handler: async (request: LensApiRequest) => { - const { cluster, params, payload, response } = request; - - try { - const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload ); - - respondJson(response, result); - } catch (error) { - logger.debug(error); - respondText(response, error?.toString() || "Error updating chart", 422); - } - }, + handler: async ({ + cluster, + params, + payload, + }: LensApiRequest): Promise => ({ + response: await helmService.updateRelease( + cluster, + params.release, + params.namespace, + payload, + ), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts b/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts index a97fafe694..6ab0bf7149 100644 --- a/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts +++ b/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts @@ -5,13 +5,11 @@ import { getInjectable } from "@ogre-tools/injectable"; import { apiPrefix } from "../../../common/vars"; -import type { LensApiRequest } from "../../router"; -import { respondJson } from "../../utils/http-responses"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { routeInjectionToken } from "../../router/router.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import { CoreV1Api, V1Secret } from "@kubernetes/client-node"; - const getServiceAccountRouteInjectable = getInjectable({ id: "get-service-account-route", @@ -19,8 +17,8 @@ const getServiceAccountRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}`, - handler: async (request: LensApiRequest) => { - const { params, response, cluster } = request; + handler: async (request: LensApiRequest): Promise => { + const { params, cluster } = request; const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api); const secretList = await client.listNamespacedSecret(params.namespace); @@ -30,9 +28,7 @@ const getServiceAccountRouteInjectable = getInjectable({ return annotations && annotations["kubernetes.io/service-account.name"] == params.account; }); - const data = generateKubeConfig(params.account, secret, cluster); - - respondJson(response, data); + return { response: generateKubeConfig(params.account, secret, cluster) }; }, }), diff --git a/src/main/routes/metrics/add-metrics-route.injectable.ts b/src/main/routes/metrics/add-metrics-route.injectable.ts index b65f5b793b..8e99c5c806 100644 --- a/src/main/routes/metrics/add-metrics-route.injectable.ts +++ b/src/main/routes/metrics/add-metrics-route.injectable.ts @@ -5,8 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { apiPrefix } from "../../../common/vars"; -import type { LensApiRequest } from "../../router"; -import { respondJson } from "../../utils/http-responses"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { routeInjectionToken } from "../../router/router.injectable"; import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../../common/cluster-types"; import logger from "../../logger"; @@ -43,7 +42,7 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa return Promise.all(queries.map(loadMetric)); } -const addMetricsRoute = async ({ response, cluster, payload, query }: LensApiRequest) => { +const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest): Promise => { const queryParams: IMetricsQuery = Object.fromEntries(query.entries()); const prometheusMetadata: ClusterPrometheusMetadata = {}; @@ -56,33 +55,39 @@ const addMetricsRoute = async ({ response, cluster, payload, query }: LensApiReq if (!prometheusPath) { prometheusMetadata.success = false; - return respondJson(response, {}); + return { response: {}}; } // return data in same structure as query if (typeof payload === "string") { const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams); - respondJson(response, data); - } else if (Array.isArray(payload)) { + return { response: data }; + } + + if (Array.isArray(payload)) { const data = await loadMetrics(payload, cluster, prometheusPath, queryParams); - respondJson(response, data); - } else { - const queries = Object.entries>(payload) - .map(([queryName, queryOpts]) => ( - provider.getQuery(queryOpts, queryName) - )); - const result = await loadMetrics(queries, cluster, prometheusPath, queryParams); - const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]])); - - respondJson(response, data); + return { response: data }; } + + const queries = Object.entries>(payload) + .map(([queryName, queryOpts]) => ( + provider.getQuery(queryOpts, queryName) + )); + + const result = await loadMetrics(queries, cluster, prometheusPath, queryParams); + const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]])); + prometheusMetadata.success = true; + + return { response: data }; } catch (error) { prometheusMetadata.success = false; - respondJson(response, {}); + logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error); + + return { response: {}}; } finally { cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata; } diff --git a/src/main/routes/metrics/get-metric-providers-route.injectable.ts b/src/main/routes/metrics/get-metric-providers-route.injectable.ts index 73a56913e3..025b8e8b4f 100644 --- a/src/main/routes/metrics/get-metric-providers-route.injectable.ts +++ b/src/main/routes/metrics/get-metric-providers-route.injectable.ts @@ -5,8 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { apiPrefix } from "../../../common/vars"; -import type { LensApiRequest } from "../../router"; -import { respondJson } from "../../utils/http-responses"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { routeInjectionToken } from "../../router/router.injectable"; import { PrometheusProviderRegistry } from "../../prometheus"; import type { MetricProviderInfo } from "../../../common/k8s-api/endpoints/metrics.api"; @@ -18,14 +17,15 @@ const getMetricProvidersRouteInjectable = getInjectable({ method: "get", path: `${apiPrefix}/metrics/providers`, - handler: async (request: LensApiRequest) => { + // eslint-disable-next-line unused-imports/no-unused-vars-ts + handler: async (request: LensApiRequest) : Promise => { const providers: MetricProviderInfo[] = []; for (const { name, id, isConfigurable } of PrometheusProviderRegistry.getInstance().providers.values()) { providers.push({ name, id, isConfigurable }); } - respondJson(request.response, providers); + return { response: providers }; }, }), diff --git a/src/main/routes/port-forward/get-current-port-forward-route.injectable.ts b/src/main/routes/port-forward/get-current-port-forward-route.injectable.ts index 4c649dfd4b..7bc80bbb2b 100644 --- a/src/main/routes/port-forward/get-current-port-forward-route.injectable.ts +++ b/src/main/routes/port-forward/get-current-port-forward-route.injectable.ts @@ -4,13 +4,12 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../router/router.injectable"; -import type { LensApiRequest } from "../../router"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { apiPrefix } from "../../../common/vars"; -import { respondJson } from "../../utils/http-responses"; import { PortForward } from "./functionality/port-forward"; -const getCurrentPortForward = async (request: LensApiRequest) => { - const { params, query, response, cluster } = request; +const getCurrentPortForward = async (request: LensApiRequest): Promise => { + const { params, query, cluster } = request; const { namespace, resourceType, resourceName } = params; const port = Number(query.get("port")); const forwardPort = Number(query.get("forwardPort")); @@ -20,7 +19,7 @@ const getCurrentPortForward = async (request: LensApiRequest) => { namespace, port, forwardPort, }); - respondJson(response, { port: portForward?.forwardPort ?? null }); + return { response: { port: portForward?.forwardPort ?? null }}; }; const getCurrentPortForwardRouteInjectable = getInjectable({ diff --git a/src/main/routes/port-forward/start-port-forward-route.injectable.ts b/src/main/routes/port-forward/start-port-forward-route.injectable.ts index 142eab3934..83e0280bd9 100644 --- a/src/main/routes/port-forward/start-port-forward-route.injectable.ts +++ b/src/main/routes/port-forward/start-port-forward-route.injectable.ts @@ -4,19 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../router/router.injectable"; -import type { LensApiRequest } from "../../router"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { apiPrefix } from "../../../common/vars"; import { PortForward, PortForwardArgs } from "./functionality/port-forward"; import logger from "../../logger"; -import { respondJson } from "../../utils/http-responses"; import createPortForwardInjectable from "./functionality/create-port-forward.injectable"; interface Dependencies { createPortForward: (pathToKubeConfig: string, args: PortForwardArgs) => PortForward; } -const startPortForward = ({ createPortForward }: Dependencies) => async (request: LensApiRequest) => { - const { params, query, response, cluster } = request; +const startPortForward = ({ createPortForward }: Dependencies) => async (request: LensApiRequest): Promise => { + const { params, query, cluster } = request; const { namespace, resourceType, resourceName } = params; const port = Number(query.get("port")); const forwardPort = Number(query.get("forwardPort")); @@ -58,32 +57,28 @@ const startPortForward = ({ createPortForward }: Dependencies) => async (request resourceName, }); - return respondJson( - response, - { + return { + error: { message: `Failed to forward port ${port} to ${ thePort ? forwardPort : "random port" }`, }, - 400, - ); + }; } } - respondJson(response, { port: portForward.forwardPort }); + return { response: { port: portForward.forwardPort }}; } catch (error) { logger.error( `[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName }, ); - return respondJson( - response, - { + return { + error: { message: `Failed to forward port ${port}`, }, - 400, - ); + }; } }; diff --git a/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts b/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts index 3919816872..3733d598c4 100644 --- a/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts +++ b/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts @@ -4,14 +4,13 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../router/router.injectable"; -import type { LensApiRequest } from "../../router"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { apiPrefix } from "../../../common/vars"; -import { respondJson } from "../../utils/http-responses"; import { PortForward } from "./functionality/port-forward"; import logger from "../../logger"; -const stopCurrentPortForward = async (request: LensApiRequest) => { - const { params, query, response, cluster } = request; +const stopCurrentPortForward = async (request: LensApiRequest): Promise => { + const { params, query, cluster } = request; const { namespace, resourceType, resourceName } = params; const port = Number(query.get("port")); const forwardPort = Number(query.get("forwardPort")); @@ -23,13 +22,17 @@ const stopCurrentPortForward = async (request: LensApiRequest) => { try { await portForward.stop(); - respondJson(response, { status: true }); + + return { response: { status: true }}; } catch (error) { logger.error("[PORT-FORWARD-ROUTE]: error stopping a port-forward", { namespace, port, forwardPort, resourceType, resourceName }); - return respondJson(response, { - message: `error stopping a forward port ${port}`, - }, 400); + return { + error: { + message: `error stopping a forward port ${port}`, + }, + }; + } }; diff --git a/src/main/routes/resource-applier/apply-resource-route.injectable.ts b/src/main/routes/resource-applier/apply-resource-route.injectable.ts index 910efd81c2..c038ba7470 100644 --- a/src/main/routes/resource-applier/apply-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/apply-resource-route.injectable.ts @@ -4,10 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../router/router.injectable"; -import type { LensApiRequest } from "../../router"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { apiPrefix } from "../../../common/vars"; import { ResourceApplier } from "../../resource-applier"; -import { respondJson, respondText } from "../../utils/http-responses"; const applyResourceRouteInjectable = getInjectable({ id: "apply-resource-route", @@ -16,17 +15,12 @@ const applyResourceRouteInjectable = getInjectable({ method: "post", path: `${apiPrefix}/stack`, - handler: async (request: LensApiRequest) => { - const { response, cluster, payload } = request; - - try { - const resource = await new ResourceApplier(cluster).apply(payload); - - respondJson(response, resource, 200); - } catch (error) { - respondText(response, error, 422); - } - }, + handler: async ({ + cluster, + payload, + }: LensApiRequest): Promise => ({ + response: await new ResourceApplier(cluster).apply(payload), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/resource-applier/patch-resource-route.injectable.ts b/src/main/routes/resource-applier/patch-resource-route.injectable.ts index bec2568a0a..900b47131f 100644 --- a/src/main/routes/resource-applier/patch-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/patch-resource-route.injectable.ts @@ -4,10 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { routeInjectionToken } from "../../router/router.injectable"; -import type { LensApiRequest } from "../../router"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { apiPrefix } from "../../../common/vars"; import { ResourceApplier } from "../../resource-applier"; -import { respondJson, respondText } from "../../utils/http-responses"; const patchResourceRouteInjectable = getInjectable({ id: "patch-resource-route", @@ -16,17 +15,17 @@ const patchResourceRouteInjectable = getInjectable({ method: "patch", path: `${apiPrefix}/stack`, - handler: async (request: LensApiRequest) => { - const { response, cluster, payload } = request; - - try { - const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.patch, payload.ns); - - respondJson(response, resource, 200); - } catch (error) { - respondText(response, error, 422); - } - }, + handler: async ({ + cluster, + payload, + }: LensApiRequest): Promise => ({ + response: await new ResourceApplier(cluster).patch( + payload.name, + payload.kind, + payload.patch, + payload.ns, + ), + }), }), injectionToken: routeInjectionToken, diff --git a/src/main/routes/static-file-route.injectable.ts b/src/main/routes/static-file-route.injectable.ts index 5074abb6c7..f238a29cbb 100644 --- a/src/main/routes/static-file-route.injectable.ts +++ b/src/main/routes/static-file-route.injectable.ts @@ -3,35 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { LensApiRequest, Route } from "../router"; +import type { LensApiRequest, LensApiResult, SupportedFileExtension, Route } from "../router"; +import { contentTypes } from "../router"; import logger from "../logger"; import { routeInjectionToken } from "../router/router.injectable"; -import { - appName, - publicPath, -} from "../../common/vars"; +import { appName, publicPath } from "../../common/vars"; import path from "path"; import readFileInjectable from "../../common/fs/read-file.injectable"; import isDevelopmentInjectable from "../../common/vars/is-development.injectable"; import httpProxy from "http-proxy"; -import { Router } from "../router"; - -function getMimeType(filename: string) { - const mimeTypes: Record = { - html: "text/html", - txt: "text/plain", - css: "text/css", - gif: "image/gif", - jpg: "image/jpeg", - png: "image/png", - svg: "image/svg+xml", - js: "application/javascript", - woff2: "font/woff2", - ttf: "font/ttf", - }; - - return mimeTypes[path.extname(filename).slice(1)] || "text/plain"; -} interface ProductionDependencies { readFile: (path: string) => Promise; @@ -39,36 +19,38 @@ interface ProductionDependencies { const handleStaticFileInProduction = ({ readFile }: ProductionDependencies) => - async ({ params, response }: LensApiRequest): Promise => { + async ({ params }: LensApiRequest): Promise => { + const staticPath = path.resolve(__static); let filePath = params.path; for (let retryCount = 0; retryCount < 5; retryCount += 1) { - const asset = path.join(Router.rootPath, filePath); + const asset = path.join(staticPath, filePath); const normalizedFilePath = path.resolve(asset); - if (!normalizedFilePath.startsWith(Router.rootPath)) { - response.statusCode = 404; - - return response.end(); + if (!normalizedFilePath.startsWith(staticPath)) { + return { statusCode: 404 }; } try { - const data = await readFile(asset); + const fileExtension = path + .extname(asset) + .slice(1) as SupportedFileExtension; - response.setHeader("Content-Type", getMimeType(asset)); - response.write(data); - response.end(); + const contentType = contentTypes[fileExtension] || contentTypes.txt; + + return { response: await readFile(asset), contentType }; } catch (err) { if (retryCount > 5) { logger.error("handleStaticFile:", err.toString()); - response.statusCode = 404; - return response.end(); + return { statusCode: 404 }; } filePath = `${publicPath}/${appName}.html`; } } + + return { statusCode: 404 }; }; interface DevelopmentDependencies { @@ -87,6 +69,8 @@ const handleStaticFileInDevelopment = proxy.web(req, res, { target: "http://127.0.0.1:8080", }); + + return { proxy }; }; const staticFileRouteInjectable = getInjectable({ diff --git a/src/main/routes/versions/get-version-route.injectable.ts b/src/main/routes/versions/get-version-route.injectable.ts index ec04f4dbaf..a32809a2a0 100644 --- a/src/main/routes/versions/get-version-route.injectable.ts +++ b/src/main/routes/versions/get-version-route.injectable.ts @@ -3,8 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { LensApiRequest } from "../../router"; -import { respondJson } from "../../utils/http-responses"; +import type { LensApiRequest, LensApiResult } from "../../router"; import { routeInjectionToken } from "../../router/router.injectable"; import { getAppVersion } from "../../../common/utils"; @@ -15,10 +14,9 @@ const getVersionRouteInjectable = getInjectable({ method: "get", path: `/version`, - handler: async (request: LensApiRequest) => { - const { response } = request; - - respondJson(response, { version: getAppVersion() }, 200); + // eslint-disable-next-line unused-imports/no-unused-vars-ts + handler: async (request: LensApiRequest): Promise => { + return { response: { version: getAppVersion() }}; }, }),