diff --git a/package.json b/package.json index d17c26f248..f8434e6658 100644 --- a/package.json +++ b/package.json @@ -411,6 +411,7 @@ "monaco-editor-webpack-plugin": "^5.0.0", "node-gyp": "^8.3.0", "node-loader": "^2.0.0", + "node-mocks-http": "^1.12.1", "nodemon": "^2.0.20", "playwright": "^1.29.2", "postcss": "^8.4.21", diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 1698997e36..1547995670 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -10,7 +10,6 @@ import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as import spawnInjectable from "./child-process/spawn.injectable"; import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable"; import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable"; -import setupLensProxyInjectable from "./start-main-application/runnables/setup-lens-proxy.injectable"; import setupSyncingOfWeblinksInjectable from "./start-main-application/runnables/setup-syncing-of-weblinks.injectable"; import setupDeepLinkingInjectable from "./electron-app/runnables/setup-deep-linking.injectable"; import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/runnables/setup-main-window-visibility-after-activation.injectable"; @@ -103,7 +102,6 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => { initializeExtensionsInjectable, initializeClusterManagerInjectable, setupIpcMainHandlersInjectable, - setupLensProxyInjectable, setupSyncingOfWeblinksInjectable, ].forEach((injectable) => { di.override(injectable, () => ({ diff --git a/src/main/lens-proxy/handle-lens-request.injectable.ts b/src/main/lens-proxy/handle-lens-request.injectable.ts new file mode 100644 index 0000000000..8e0e6ca5ec --- /dev/null +++ b/src/main/lens-proxy/handle-lens-request.injectable.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import assert from "assert"; +import type { IncomingMessage } from "http"; +import HttpProxyServer from "http-proxy"; +import { Socket } from "net"; +import loggerInjectable from "../../common/logger.injectable"; +import type { HandleRequest } from "./handle-request-for.injectable"; +import handleRequestForInjectable from "./handle-request-for.injectable"; + +const getRequestId = (req: IncomingMessage) => { + assert(req.headers.host); + + return req.headers.host + req.url; +}; + +export interface HandleLensRequest { + handle: HandleRequest; + stopHandling: () => void; +} + +const handleLensRequestInjectable = getInjectable({ + id: "handle-lens-request", + instantiate: (di): HandleLensRequest => { + const logger = di.inject(loggerInjectable); + const handleRequestFor = di.inject(handleRequestForInjectable); + + const retryCounters = new Map(); + let closed = false; + + const proxy = HttpProxyServer.createProxy() + .on("proxyRes", (proxyRes, req, res) => { + retryCounters.delete(getRequestId(req)); + + proxyRes.on("aborted", () => { // happens when proxy target aborts connection + res.end(); + }); + }) + .on("error", (error, req, res, target) => { + if (closed || res instanceof Socket) { + return; + } + + logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${error}`, { url: req.url }); + + if (target) { + logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`); + + if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) { + const reqId = getRequestId(req); + const retryCount = retryCounters.get(reqId) || 0; + const timeoutMs = retryCount * 250; + + if (retryCount < 20) { + logger.debug(`Retrying proxy request to url: ${reqId}`); + setTimeout(() => { + retryCounters.set(reqId, retryCount + 1); + + (async () => { + try { + await handleRequest(req, res); + } catch (error) { + logger.error(`[LENS-PROXY]: failed to handle request on proxy error: ${error}`); + } + })(); + }, timeoutMs); + } + } + } + + try { + res.writeHead(500).end(`Oops, something went wrong.\n${error}`); + } catch (e) { + logger.error(`[LENS-PROXY]: Failed to write headers: `, e); + } + }); + + const handleRequest = handleRequestFor(proxy); + + return { + handle: handleRequest, + stopHandling: () => closed = true, + }; + }, +}); + +export default handleLensRequestInjectable; diff --git a/src/main/lens-proxy/handle-request-for.injectable.ts b/src/main/lens-proxy/handle-request-for.injectable.ts new file mode 100644 index 0000000000..0eff5110ce --- /dev/null +++ b/src/main/lens-proxy/handle-request-for.injectable.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { ServerResponse, IncomingMessage } from "http"; +import { apiKubePrefix } from "../../common/vars"; +import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable"; +import type { ClusterContextHandler } from "../context-handler/context-handler"; +import routeRequestInjectable from "../router/route-request.injectable"; +import { getBoolean } from "../utils/parse-query"; +import getClusterForRequestInjectable from "./get-cluster-for-request.injectable"; +import type HttpProxyServer from "http-proxy"; + +const watchParam = "watch"; +const followParam = "follow"; + +const isLongRunningRequest = (reqUrl: string) => { + const url = new URL(reqUrl, "http://localhost"); + + return getBoolean(url.searchParams, watchParam) || getBoolean(url.searchParams, followParam); +}; + +export type HandleRequest = (req: IncomingMessage, res: ServerResponse) => Promise; +export type HandleRequestFor = (proxy: HttpProxyServer) => HandleRequest; + +const handleRequestForInjectable = getInjectable({ + id: "handle-request-for", + instantiate: (di): HandleRequestFor => { + const getClusterForRequest = di.inject(getClusterForRequestInjectable); + const contentSecurityPolicy = di.inject(contentSecurityPolicyInjectable); + const routeRequest = di.inject(routeRequestInjectable); + + const getProxyTarget = async (req: IncomingMessage, contextHandler: ClusterContextHandler) => { + if (req.url?.startsWith(apiKubePrefix)) { + delete req.headers.authorization; + req.url = req.url.replace(apiKubePrefix, ""); + + return contextHandler.getApiTarget(isLongRunningRequest(req.url)); + } + + return undefined; + }; + + return (proxy) => async (req, res) => { + const cluster = getClusterForRequest(req); + + if (cluster) { + const proxyTarget = await getProxyTarget(req, cluster.contextHandler); + + if (proxyTarget) { + return proxy.web(req, res, proxyTarget); + } + } + + res.setHeader("Content-Security-Policy", contentSecurityPolicy); + await routeRequest(cluster, req, res); + }; + }, +}); + +export default handleRequestForInjectable; diff --git a/src/main/lens-proxy/lens-proxy.injectable.ts b/src/main/lens-proxy/lens-proxy.injectable.ts index 852a766db2..2bc2acef99 100644 --- a/src/main/lens-proxy/lens-proxy.injectable.ts +++ b/src/main/lens-proxy/lens-proxy.injectable.ts @@ -5,23 +5,18 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { ProxyApiRequestArgs } from "./proxy-functions"; import { kubeApiUpgradeRequest } from "./proxy-functions"; -import httpProxy from "http-proxy"; import shellApiRequestInjectable from "./proxy-functions/shell-api-request.injectable"; import lensProxyPortInjectable from "./lens-proxy-port.injectable"; -import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable"; import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; import loggerInjectable from "../../common/logger.injectable"; import lensProxyCertificateInjectable from "../../common/certificate/lens-proxy-certificate.injectable"; import getClusterForRequestInjectable from "./get-cluster-for-request.injectable"; -import routeRequestInjectable from "../router/route-request.injectable"; -import type { IncomingMessage, ServerResponse } from "http"; -import assert from "assert"; -import net from "net"; +import type { IncomingMessage } from "http"; +import type net from "net"; import type { Cluster } from "../../common/cluster/cluster"; -import { getBoolean } from "../utils/parse-query"; -import type { ClusterContextHandler } from "../context-handler/context-handler"; -import { apiKubePrefix, apiPrefix } from "../../common/vars"; +import { apiPrefix } from "../../common/vars"; import { createServer } from "https"; +import handleLensRequestInjectable from "./handle-lens-request.injectable"; export type GetClusterForRequest = (req: IncomingMessage) => Cluster | undefined; export type LensProxyApiRequest = (args: ProxyApiRequestArgs) => void | Promise; @@ -31,21 +26,6 @@ export interface LensProxy { close: () => void; } -const getRequestId = (req: IncomingMessage) => { - assert(req.headers.host); - - return req.headers.host + req.url; -}; - -const watchParam = "watch"; -const followParam = "follow"; - -const isLongRunningRequest = (reqUrl: string) => { - const url = new URL(reqUrl, "http://localhost"); - - return getBoolean(url.searchParams, watchParam) || getBoolean(url.searchParams, followParam); -}; - /** * This is the list of ports that chrome considers unsafe to allow HTTP * conntections to. Because they are the standard ports for processes that are @@ -68,97 +48,20 @@ const lensProxyInjectable = getInjectable({ id: "lens-proxy", instantiate: (di): LensProxy => { - const routeRequest = di.inject(routeRequestInjectable); const shellApiRequest = di.inject(shellApiRequestInjectable); const getClusterForRequest = di.inject(getClusterForRequestInjectable); const lensProxyPort = di.inject(lensProxyPortInjectable); - const contentSecurityPolicy = di.inject(contentSecurityPolicyInjectable); const emitAppEvent = di.inject(emitAppEventInjectable); const logger = di.inject(loggerInjectable); const certificate = di.inject(lensProxyCertificateInjectable).get(); - - const retryCounters = new Map(); - let closed = false; - - const proxy = httpProxy.createProxy() - .on("proxyRes", (proxyRes, req, res) => { - retryCounters.delete(getRequestId(req)); - - proxyRes.on("aborted", () => { // happens when proxy target aborts connection - res.end(); - }); - }) - .on("error", (error, req, res, target) => { - if (closed || res instanceof net.Socket) { - return; - } - - logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${error}`, { url: req.url }); - - if (target) { - logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`); - - if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) { - const reqId = getRequestId(req); - const retryCount = retryCounters.get(reqId) || 0; - const timeoutMs = retryCount * 250; - - if (retryCount < 20) { - logger.debug(`Retrying proxy request to url: ${reqId}`); - setTimeout(() => { - retryCounters.set(reqId, retryCount + 1); - - (async () => { - try { - await handleRequest(req, res); - } catch (error) { - logger.error(`[LENS-PROXY]: failed to handle request on proxy error: ${error}`); - } - })(); - }, timeoutMs); - } - } - } - - try { - res.writeHead(500).end(`Oops, something went wrong.\n${error}`); - } catch (e) { - logger.error(`[LENS-PROXY]: Failed to write headers: `, e); - } - }); - - const getProxyTarget = async (req: IncomingMessage, contextHandler: ClusterContextHandler) => { - if (req.url?.startsWith(apiKubePrefix)) { - delete req.headers.authorization; - req.url = req.url.replace(apiKubePrefix, ""); - - return contextHandler.getApiTarget(isLongRunningRequest(req.url)); - } - - return undefined; - }; - - const handleRequest = async (req: IncomingMessage, res: ServerResponse) => { - const cluster = getClusterForRequest(req); - - if (cluster) { - const proxyTarget = await getProxyTarget(req, cluster.contextHandler); - - if (proxyTarget) { - return proxy.web(req, res, proxyTarget); - } - } - - res.setHeader("Content-Security-Policy", contentSecurityPolicy); - await routeRequest(cluster, req, res); - }; + const handleLensRequest = di.inject(handleLensRequestInjectable); const proxyServer = createServer( { key: certificate.private, cert: certificate.cert, }, - handleRequest, + handleLensRequest.handle, ) .on("upgrade", (req, socket, head) => { const cluster = getClusterForRequest(req); @@ -238,7 +141,7 @@ const lensProxyInjectable = getInjectable({ logger.info("[LENS-PROXY]: Closing server"); proxyServer.close(); - closed = true; + handleLensRequest.stopHandling(); }; return { close, listen }; diff --git a/src/main/router/create-handler-for-route.injectable.ts b/src/main/router/create-handler-for-route.injectable.ts index 456a9dc94a..d2680152ff 100644 --- a/src/main/router/create-handler-for-route.injectable.ts +++ b/src/main/router/create-handler-for-route.injectable.ts @@ -5,8 +5,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { ServerResponse } from "http"; import loggerInjectable from "../../common/logger.injectable"; -import { lensAuthenticationHeader } from "../../common/vars/auth-header"; -import authHeaderValueInjectable from "../lens-proxy/auth-header-value.injectable"; import type { LensApiRequest, Route } from "./route"; import { contentTypes } from "./router-content-types"; import { writeServerResponseFor } from "./write-server-response"; @@ -18,24 +16,10 @@ const createHandlerForRouteInjectable = getInjectable({ id: "create-handler-for-route", instantiate: (di): CreateHandlerForRoute => { const logger = di.inject(loggerInjectable); - const authHeaderValue = `Bearer ${di.inject(authHeaderValueInjectable)}`; return (route) => async (request, response) => { const writeServerResponse = writeServerResponseFor(response); - if (route.requireAuthentication) { - const authHeader = request.getHeader(lensAuthenticationHeader); - - if (authHeader !== authHeaderValue) { - writeServerResponse(contentTypes.txt.resultMapper({ - statusCode: 401, - response: "Missing authorization", - })); - - return; - } - } - try { const result = await route.handler(request); diff --git a/src/main/routes/files/static-file-route.injectable.ts b/src/main/routes/files/static-file-route.injectable.ts index 0268ac2051..ec46b19d59 100644 --- a/src/main/routes/files/static-file-route.injectable.ts +++ b/src/main/routes/files/static-file-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import isDevelopmentInjectable from "../../../common/vars/is-development.injectable"; import { route } from "../../router/route"; import prodStaticFileRouteHandlerInjectable from "./production.injectable"; diff --git a/src/main/routes/helm/charts/get-readme-route.injectable.ts b/src/main/routes/helm/charts/get-readme-route.injectable.ts index f3b52073c2..c72fdd7193 100644 --- a/src/main/routes/helm/charts/get-readme-route.injectable.ts +++ b/src/main/routes/helm/charts/get-readme-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { apiPrefix } from "../../../../common/vars"; import { route } from "../../../router/route"; import getHelmChartReadmeInjectable from "../../../helm/helm-service/get-helm-chart-readme.injectable"; diff --git a/src/main/routes/helm/charts/get-values-route.injectable.ts b/src/main/routes/helm/charts/get-values-route.injectable.ts index 49efe5fb0c..b997766c7b 100644 --- a/src/main/routes/helm/charts/get-values-route.injectable.ts +++ b/src/main/routes/helm/charts/get-values-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { apiPrefix } from "../../../../common/vars"; import { route } from "../../../router/route"; import getHelmChartValuesInjectable from "../../../helm/helm-service/get-helm-chart-values.injectable"; diff --git a/src/main/routes/helm/charts/get-versions-route.injectable.ts b/src/main/routes/helm/charts/get-versions-route.injectable.ts index 933d94743b..a0689f2c22 100644 --- a/src/main/routes/helm/charts/get-versions-route.injectable.ts +++ b/src/main/routes/helm/charts/get-versions-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { apiPrefix } from "../../../../common/vars"; import { route } from "../../../router/route"; import getHelmChartVersionsInjectable from "../../../helm/helm-service/get-helm-chart-versions.injectable"; diff --git a/src/main/routes/helm/charts/list-route.injectable.ts b/src/main/routes/helm/charts/list-route.injectable.ts index 402a3d7cd1..7dbcc1f522 100644 --- a/src/main/routes/helm/charts/list-route.injectable.ts +++ b/src/main/routes/helm/charts/list-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { apiPrefix } from "../../../../common/vars"; import { route } from "../../../router/route"; import listHelmChartsInjectable from "../../../helm/helm-service/list-helm-charts.injectable"; 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 79d3195643..4b1fa92a6e 100644 --- a/src/main/routes/helm/releases/delete-release-route.injectable.ts +++ b/src/main/routes/helm/releases/delete-release-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { clusterRoute } from "../../../router/route"; import deleteClusterHelmReleaseInjectable from "../../../helm/helm-service/delete-helm-release.injectable"; 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 2ba1349de0..db2892b978 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,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { clusterRoute } from "../../../router/route"; import getClusterHelmReleaseHistoryInjectable from "../../../helm/helm-service/get-helm-release-history.injectable"; 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 7b264b4fe9..4c6b289ec0 100644 --- a/src/main/routes/helm/releases/get-release-route.injectable.ts +++ b/src/main/routes/helm/releases/get-release-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { clusterRoute } from "../../../router/route"; import getHelmReleaseInjectable from "../../../helm/helm-service/get-helm-release.injectable"; 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 78799f3e58..21e30a4c98 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,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { getBoolean } from "../../../utils/parse-query"; import { contentTypes } from "../../../router/router-content-types"; import { clusterRoute } from "../../../router/route"; 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 e34d54db23..d6d8619b50 100644 --- a/src/main/routes/helm/releases/install-chart-route.injectable.ts +++ b/src/main/routes/helm/releases/install-chart-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import Joi from "joi"; import { payloadValidatedClusterRoute } from "../../../router/route"; import type { InstallChartArgs } from "../../../helm/helm-service/install-helm-chart.injectable"; 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 53a4dcb179..4798956cfd 100644 --- a/src/main/routes/helm/releases/list-releases-route.injectable.ts +++ b/src/main/routes/helm/releases/list-releases-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { clusterRoute } from "../../../router/route"; import listClusterHelmReleasesInjectable from "../../../helm/helm-service/list-helm-releases.injectable"; 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 49a8fddb88..e08d835620 100644 --- a/src/main/routes/helm/releases/rollback-release-route.injectable.ts +++ b/src/main/routes/helm/releases/rollback-release-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import Joi from "joi"; import { payloadValidatedClusterRoute } from "../../../router/route"; import rollbackClusterHelmReleaseInjectable from "../../../helm/helm-service/rollback-helm-release.injectable"; 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 5993d7922d..1b062cf439 100644 --- a/src/main/routes/helm/releases/update-release-route.injectable.ts +++ b/src/main/routes/helm/releases/update-release-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { apiPrefix } from "../../../../common/vars"; -import { getRouteInjectable } from "../../../router/router.injectable"; +import { getRouteInjectable } from "../../../router/route-request.injectable"; import { payloadValidatedClusterRoute } from "../../../router/route"; import Joi from "joi"; import type { UpdateChartArgs } from "../../../helm/helm-service/update-helm-release.injectable"; 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 476ee983a3..34bd0d789c 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 @@ -4,7 +4,7 @@ */ import { apiPrefix } from "../../../common/vars"; -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import type { V1Secret } from "@kubernetes/client-node"; import { CoreV1Api } from "@kubernetes/client-node"; diff --git a/src/main/routes/metrics/add-metrics-route.injectable.ts b/src/main/routes/metrics/add-metrics-route.injectable.ts index eb59f2840b..a9a239717c 100644 --- a/src/main/routes/metrics/add-metrics-route.injectable.ts +++ b/src/main/routes/metrics/add-metrics-route.injectable.ts @@ -4,7 +4,7 @@ */ import { apiPrefix } from "../../../common/vars"; -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import type { ClusterPrometheusMetadata } from "../../../common/cluster-types"; import { ClusterMetadataKey } from "../../../common/cluster-types"; import type { Cluster } from "../../../common/cluster/cluster"; 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 54dfd9bb95..909b368411 100644 --- a/src/main/routes/metrics/get-metric-providers-route.injectable.ts +++ b/src/main/routes/metrics/get-metric-providers-route.injectable.ts @@ -4,7 +4,7 @@ */ import { apiPrefix } from "../../../common/vars"; -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { route } from "../../router/route"; import prometheusProvidersInjectable from "../../prometheus/providers.injectable"; 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 769046c804..46c2db3f11 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 @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { apiPrefix } from "../../../common/vars"; import { PortForward } from "./functionality/port-forward"; import { clusterRoute } from "../../router/route"; 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 0716b81a2f..e0aec149f1 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 @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { apiPrefix } from "../../../common/vars"; import { PortForward } from "./functionality/port-forward"; import createPortForwardInjectable from "./functionality/create-port-forward.injectable"; 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 1fc76e9b72..98dab3b995 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 @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { apiPrefix } from "../../../common/vars"; import { PortForward } from "./functionality/port-forward"; import { clusterRoute } from "../../router/route"; diff --git a/src/main/routes/resource-applier/create-resource-route.injectable.ts b/src/main/routes/resource-applier/create-resource-route.injectable.ts index 3b68201e9d..079f1dc147 100644 --- a/src/main/routes/resource-applier/create-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/create-resource-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { apiPrefix } from "../../../common/vars"; import { payloadValidatedClusterRoute } from "../../router/route"; import Joi from "joi"; 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 e962c18607..8773e11e09 100644 --- a/src/main/routes/resource-applier/patch-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/patch-resource-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { apiPrefix } from "../../../common/vars"; import { payloadValidatedClusterRoute } from "../../router/route"; import Joi from "joi"; diff --git a/src/main/routes/versions/get-version-route.injectable.ts b/src/main/routes/versions/get-version-route.injectable.ts index d132e242d6..c3a8f9304a 100644 --- a/src/main/routes/versions/get-version-route.injectable.ts +++ b/src/main/routes/versions/get-version-route.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getRouteInjectable } from "../../router/router.injectable"; +import { getRouteInjectable } from "../../router/route-request.injectable"; import { route } from "../../router/route"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; diff --git a/src/main/start-main-application/runnables/setup-lens-proxy.injectable.ts b/src/main/start-main-application/runnables/setup-lens-proxy.injectable.ts index 69184ddec2..6c2e071abc 100644 --- a/src/main/start-main-application/runnables/setup-lens-proxy.injectable.ts +++ b/src/main/start-main-application/runnables/setup-lens-proxy.injectable.ts @@ -6,15 +6,12 @@ import { getInjectable } from "@ogre-tools/injectable"; import exitAppInjectable from "../../electron-app/features/exit-app.injectable"; import lensProxyInjectable from "../../lens-proxy/lens-proxy.injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import lensProxyPortInjectable from "../../lens-proxy/lens-proxy-port.injectable"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import showErrorPopupInjectable from "../../electron-app/features/show-error-popup.injectable"; import { beforeApplicationIsLoadingInjectionToken } from "../runnable-tokens/before-application-is-loading-injection-token"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; import initializeBuildVersionInjectable from "../../vars/build-version/init.injectable"; -import lensProxyCertificateInjectable from "../../../common/certificate/lens-proxy-certificate.injectable"; -import fetchInjectable from "../../../common/fetch/fetch.injectable"; -import { Agent } from "https"; +import lensFetchInjectable from "../../../common/fetch/lens-fetch.injectable"; const setupLensProxyInjectable = getInjectable({ id: "setup-lens-proxy", @@ -23,12 +20,10 @@ const setupLensProxyInjectable = getInjectable({ const lensProxy = di.inject(lensProxyInjectable); const exitApp = di.inject(exitAppInjectable); const logger = di.inject(loggerInjectable); - const lensProxyPort = di.inject(lensProxyPortInjectable); const isWindows = di.inject(isWindowsInjectable); const showErrorPopup = di.inject(showErrorPopupInjectable); const buildVersion = di.inject(buildVersionInjectable); - const lensProxyCertificate = di.inject(lensProxyCertificateInjectable); - const fetch = di.inject(fetchInjectable); + const lensFetch = di.inject(lensFetchInjectable); return { id: "setup-lens-proxy", @@ -45,11 +40,7 @@ const setupLensProxyInjectable = getInjectable({ // test proxy connection try { logger.info("🔎 Testing LensProxy connection ..."); - const versionResponse = await fetch(`https://127.0.0.1:${lensProxyPort.get()}/version`, { - agent: new Agent({ - ca: lensProxyCertificate.get()?.cert, - }), - }); + const versionResponse = await lensFetch("/version"); const { version: versionFromProxy } = await versionResponse.json() as { version: string }; @@ -81,9 +72,6 @@ const setupLensProxyInjectable = getInjectable({ runAfter: di.inject(initializeBuildVersionInjectable), }; }, - - causesSideEffects: true, - injectionToken: beforeApplicationIsLoadingInjectionToken, }); diff --git a/src/renderer/components/test-utils/get-application-builder.tsx b/src/renderer/components/test-utils/get-application-builder.tsx index 41f26803d4..8de24c5593 100644 --- a/src/renderer/components/test-utils/get-application-builder.tsx +++ b/src/renderer/components/test-utils/get-application-builder.tsx @@ -69,6 +69,11 @@ import fsInjectable from "../../../common/fs/fs.injectable"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable"; import { testUsingFakeTime } from "../../../common/test-utils/use-fake-time"; +import lensFetchInjectable from "../../../common/fetch/lens-fetch.injectable"; +import handleLensRequestInjectable from "../../../main/lens-proxy/handle-lens-request.injectable"; +import httpMocks from "node-mocks-http"; +import nodeFetchModuleInjectable from "../../../common/fetch/fetch-module.injectable"; +import stream from "stream"; type Callback = (di: DiContainer) => void | Promise; @@ -213,6 +218,35 @@ export const getApplicationBuilder = () => { }, })); + mainDi.override(lensFetchInjectable, (di) => { + return async (pathnameAndQuery, init) => { + const handleLensRequest = di.inject(handleLensRequestInjectable); + const { Headers, Response } = di.inject(nodeFetchModuleInjectable); + + const url = new URL(pathnameAndQuery, "https://127.0.0.1"); + const req = httpMocks.createRequest({ + method: (init?.method ?? "get").toUpperCase() as any, + url: url.pathname, + params: url.searchParams, + body: (init?.body ?? undefined) as any, + headers: new Headers(init?.headers) as any, + }); + const duplex = new stream.Duplex(); + const res = httpMocks.createResponse({ + req, + writableStream: duplex, + }); + + await handleLensRequest.handle(req, res); + + return new Response(duplex, { + headers: new Headers(res._getHeaders() as Record), + status: res._getStatusCode(), + statusText: res._getStatusMessage(), + }); + }; + }); + const allowedResourcesState = observable.set(); const windowHelpers = new Map RenderResult }>(); diff --git a/yarn.lock b/yarn.lock index 7a28c3c71b..fba376de4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3109,6 +3109,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@^1.3.7, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + accepts@~1.3.4, accepts@~1.3.5: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -3117,14 +3125,6 @@ accepts@~1.3.4, accepts@~1.3.5: mime-types "~2.1.24" negotiator "0.6.2" -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -4619,7 +4619,7 @@ console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -content-disposition@0.5.4, content-disposition@^0.5.2: +content-disposition@0.5.4, content-disposition@^0.5.2, content-disposition@^0.5.3: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -5130,7 +5130,7 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@^1.1.2, depd@~1.1.2: +depd@^1.1.0, depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -6611,7 +6611,7 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -9346,7 +9346,7 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== -merge-descriptors@1.0.1: +merge-descriptors@1.0.1, merge-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= @@ -9366,7 +9366,7 @@ mergee@^1.0.0: resolved "https://registry.yarnpkg.com/mergee/-/mergee-1.0.0.tgz#027c5addc650f6ecbe4bf56100bd00dae763fda7" integrity sha512-hbbXD4LOcxVkpS+mp3BMEhkSDf+lTVENFeEeqACgjjL8WrgKuW2EyLT0fOHyTbyDiuRLZJZ1HrHNeiX4iOd79Q== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -9391,7 +9391,7 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.4.1: +mime@1.6.0, mime@^1.3.4, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -9817,6 +9817,22 @@ node-loader@^2.0.0: dependencies: loader-utils "^2.0.0" +node-mocks-http@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.12.1.tgz#838e176019daf177caff6bb8534e3a32646e7531" + integrity sha512-jrA7Sn3qI6GsHgWtUW3gMj0vO6Yz0nJjzg3jRZYjcfj4tzi8oWPauDK1qHVJoAxTbwuDHF1JiM9GISZ/ocI/ig== + dependencies: + accepts "^1.3.7" + content-disposition "^0.5.3" + depd "^1.1.0" + fresh "^0.5.2" + merge-descriptors "^1.0.1" + methods "^1.1.2" + mime "^1.3.4" + parseurl "^1.3.3" + range-parser "^1.2.0" + type-is "^1.6.18" + node-pty@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.1.tgz#cd05d03a2710315ec40221232ec04186f6ac2c6d" @@ -10512,7 +10528,7 @@ parse5@6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -11104,7 +11120,7 @@ randomcolor@^0.6.2: resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.6.2.tgz#7a57362ae1a1278439aeed2c15e5deb8ea33f56d" integrity sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A== -range-parser@^1.2.1, range-parser@~1.2.1: +range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== @@ -13070,7 +13086,7 @@ type-fest@^2.12.2, type-fest@^2.14.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.14.0.tgz#f990e19169517d689c98e16d128b231022b27e12" integrity sha512-hQnTQkFjL5ik6HF2fTAM8ycbr94UbQXK364wF930VHb0dfBJ5JBP8qwrR8TaK9zwUEk7meruo2JAUDMwvuxd/w== -type-is@~1.6.18: +type-is@^1.6.18, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==