mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Making apiBase injectable (#6022)
* Making apiBase injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all of Helm functions to be DI Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make PortForward's use of apiBase fully injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all metric requests to be injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace resource applier with injectables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch KubeJsonApi.forCluster to be injectable but do not use Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert the rest of shell sessions to be DI-ed - This is a prerequesit for using the new createKubeJsonApiForClusterInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use new createKubeJsonApiForClusterInjectable for openNodeShellSession Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make KubeconfigDialog injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove jest-fetch-mock and make fetch injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests with new global override Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add new injectable for create KubeJsonApi and JsonApi instances Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix showing-details-for-helm-release behavioural tests - Remove HelmChartStore in favour of all injectables - Create a model for UpgradeChartDockTab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix show details and updating helm releases tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix residual typing issues related to metrics Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash on load due to circular dependency Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix create resource tab not working Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove legacy apiBase global Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use isDebuggingInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use windowLocationInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove global legacy apiKube Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve injectable filenames compared to the injectables inside Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove modifying input in requestActivePortForwardInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use get(Milli)SecondsFromUnixEpochInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch to non-reactive way of gettting possible helm release versions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix typo Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bug in KubeApi constructor Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all KubeApi related tests to use asyncFn Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests after introducing new injectables that have side effects Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bad rebase causing tests to fail Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve expects for multiple field values Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash will looking up api refs Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix breaking change on KubeApi.list Signed-off-by: Sebastian Malton <sebastian@malton.name> * Better fix for formatting urls Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove injectable for time since we should just use useMockTime Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add happy path behavioural tests for upgrade chart tab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove debug message Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix showing-details-for-helm-release tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix installing-helm-chart-from-new-tab tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests relating to hosted cluster id Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots to recent changes in master Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Reupdated upgrade chart new tab test snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness in unit test when using <Animated> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness and improve tests for DeleteClusterDialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kubeconfig-sync tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix <Extensions> tests by removing mockFs and making everything injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix getElectronAppPathInjectable override not returning absolute paths - Also fixes the listing-active-helm-repos-in-prefs tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace all uses of getAbsolutePath with joinPaths as it is more correct and less confusing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix opening application window tests by making override properly absolute Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots relating no longer using getAbsolutePath Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix and add behavioural tests for RenderDelay Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test flakiness because of path side effects, propagate uses to as many places Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension-discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override to fix some tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rewrite and fix implementation of KubeconfigManager and its tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by global override pathExists Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests failing on windows by using injectable verions of path functions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Attempt to fix test timeout by using runInAction Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix setupIpcMainHandlers usage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
367 files changed
+16701
-5302
No files matched your search
@@ -5,25 +5,30 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import localShellSessionInjectable from "./local-shell-session/local-shell-session.injectable";
|
||||
import nodeShellSessionInjectable from "./node-shell-session/node-shell-session.injectable";
|
||||
import openLocalShellSessionInjectable from "./local-shell-session/open.injectable";
|
||||
import openNodeShellSessionInjectable from "./node-shell-session/open.injectable";
|
||||
|
||||
interface Args {
|
||||
webSocket: WebSocket;
|
||||
export interface OpenShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName?: string;
|
||||
}
|
||||
|
||||
const createShellSessionInjectable = getInjectable({
|
||||
id: "create-shell-session",
|
||||
export type OpenShellSession = (args: OpenShellSessionArgs) => Promise<void>;
|
||||
|
||||
instantiate:
|
||||
(di) =>
|
||||
({ nodeName, ...rest }: Args) =>
|
||||
!nodeName
|
||||
? di.inject(localShellSessionInjectable, rest)
|
||||
: di.inject(nodeShellSessionInjectable, { nodeName, ...rest }),
|
||||
const openShellSessionInjectable = getInjectable({
|
||||
id: "open-shell-session",
|
||||
|
||||
instantiate: (di): OpenShellSession => {
|
||||
const openLocalShellSession = di.inject(openLocalShellSessionInjectable);
|
||||
|
||||
return ({ nodeName, ...args }) => (
|
||||
nodeName
|
||||
? di.inject(openNodeShellSessionInjectable, { nodeName, ...args })
|
||||
: openLocalShellSession(args)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default createShellSessionInjectable;
|
||||
export default openShellSessionInjectable;
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { LocalShellSession } from "./local-shell-session";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import terminalShellEnvModifiersInjectable from "../shell-env-modifier/terminal-shell-env-modify.injectable";
|
||||
|
||||
interface InstantiationParameter {
|
||||
webSocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
const localShellSessionInjectable = getInjectable({
|
||||
id: "local-shell-session",
|
||||
|
||||
instantiate: (di, { cluster, tabId, webSocket }: InstantiationParameter) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const localShellEnvModify = di.inject(terminalShellEnvModifiersInjectable);
|
||||
|
||||
const kubectl = createKubectl(cluster.version);
|
||||
|
||||
return new LocalShellSession(localShellEnvModify, kubectl, webSocket, cluster, tabId);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default localShellSessionInjectable;
|
||||
@@ -3,24 +3,32 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type WebSocket from "ws";
|
||||
import path from "path";
|
||||
import { UserStore } from "../../../common/user-store";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import type { UserStore } from "../../../common/user-store";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellSession } from "../shell-session";
|
||||
import type { Kubectl } from "../../kubectl/kubectl";
|
||||
import { baseBinariesDir } from "../../../common/vars";
|
||||
import type { ModifyTerminalShellEnv } from "../shell-env-modifier/modify-terminal-shell-env.injectable";
|
||||
import type { JoinPaths } from "../../../common/path/join-paths.injectable";
|
||||
import type { GetDirnameOfPath } from "../../../common/path/get-dirname.injectable";
|
||||
import type { GetBasenameOfPath } from "../../../common/path/get-basename.injectable";
|
||||
|
||||
export interface LocalShellSessionDependencies extends ShellSessionDependencies {
|
||||
readonly directoryForBinaries: string;
|
||||
readonly userStore: UserStore;
|
||||
modifyTerminalShellEnv: ModifyTerminalShellEnv;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
getBasenameOfPath: GetBasenameOfPath;
|
||||
}
|
||||
|
||||
export class LocalShellSession extends ShellSession {
|
||||
ShellType = "shell";
|
||||
|
||||
constructor(protected shellEnvModify: (clusterId: ClusterId, env: Record<string, string | undefined>) => Record<string, string | undefined>, kubectl: Kubectl, websocket: WebSocket, cluster: Cluster, terminalId: string) {
|
||||
super(kubectl, websocket, cluster, terminalId);
|
||||
constructor(protected readonly dependencies: LocalShellSessionDependencies, args: ShellSessionArgs) {
|
||||
super(dependencies, args);
|
||||
}
|
||||
|
||||
protected getPathEntries(): string[] {
|
||||
return [baseBinariesDir.get()];
|
||||
return [this.dependencies.directoryForBinaries];
|
||||
}
|
||||
|
||||
protected get cwd(): string | undefined {
|
||||
@@ -31,7 +39,7 @@ export class LocalShellSession extends ShellSession {
|
||||
let env = await this.getCachedShellEnv();
|
||||
|
||||
// extensions can modify the env
|
||||
env = this.shellEnvModify(this.cluster.id, env);
|
||||
env = this.dependencies.modifyTerminalShellEnv(this.cluster.id, env);
|
||||
|
||||
const shell = env.PTYSHELL;
|
||||
|
||||
@@ -45,16 +53,18 @@ export class LocalShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||
const pathFromPreferences = UserStore.getInstance().kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = UserStore.getInstance().downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
const pathFromPreferences = this.dependencies.userStore.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = this.dependencies.userStore.downloadKubectlBinaries
|
||||
? await this.kubectlBinDirP
|
||||
: this.dependencies.getDirnameOfPath(pathFromPreferences);
|
||||
|
||||
switch(path.basename(shell)) {
|
||||
switch(this.dependencies.getBasenameOfPath(shell)) {
|
||||
case "powershell.exe":
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${baseBinariesDir.get()};$Env:PATH"}`];
|
||||
return ["-NoExit", "-command", `& {$Env:PATH="${kubectlPathDir};${this.dependencies.directoryForBinaries};$Env:PATH"}`];
|
||||
case "bash":
|
||||
return ["--init-file", path.join(await this.kubectlBinDirP, ".bash_set_path")];
|
||||
return ["--init-file", this.dependencies.joinPaths(await this.kubectlBinDirP, ".bash_set_path")];
|
||||
case "fish":
|
||||
return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${baseBinariesDir.get()}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
return ["--login", "--init-command", `export PATH="${kubectlPathDir}:${this.dependencies.directoryForBinaries}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
case "zsh":
|
||||
return ["--login"];
|
||||
default:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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 { LocalShellSessionDependencies } from "./local-shell-session";
|
||||
import { LocalShellSession } from "./local-shell-session";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import modifyTerminalShellEnvInjectable from "../shell-env-modifier/modify-terminal-shell-env.injectable";
|
||||
import directoryForBinariesInjectable from "../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable";
|
||||
import isMacInjectable from "../../../common/vars/is-mac.injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
|
||||
import type WebSocket from "ws";
|
||||
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
|
||||
export interface OpenLocalShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export type OpenLocalShellSession = (args: OpenLocalShellSessionArgs) => Promise<void>;
|
||||
|
||||
const openLocalShellSessionInjectable = getInjectable({
|
||||
id: "open-local-shell-session",
|
||||
|
||||
instantiate: (di): OpenLocalShellSession => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const dependencies: LocalShellSessionDependencies = {
|
||||
directoryForBinaries: di.inject(directoryForBinariesInjectable),
|
||||
isMac: di.inject(isMacInjectable),
|
||||
modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable),
|
||||
isWindows: di.inject(isWindowsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
userStore: di.inject(userStoreInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||
};
|
||||
|
||||
return (args) => {
|
||||
const kubectl = createKubectl(args.cluster.version);
|
||||
const session = new LocalShellSession(dependencies, { kubectl, ...args });
|
||||
|
||||
return session.open();
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default openLocalShellSessionInjectable;
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import { NodeShellSession } from "./node-shell-session";
|
||||
|
||||
interface InstantiationParameter {
|
||||
webSocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
const nodeShellSessionInjectable = getInjectable({
|
||||
id: "node-shell-session",
|
||||
|
||||
instantiate: (di, { cluster, tabId, webSocket, nodeName }: InstantiationParameter) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
|
||||
const kubectl = createKubectl(cluster.version);
|
||||
|
||||
return new NodeShellSession(nodeName, kubectl, webSocket, cluster, tabId);
|
||||
},
|
||||
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default nodeShellSessionInjectable;
|
||||
@@ -3,28 +3,34 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type WebSocket from "ws";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { Watch, CoreV1Api } from "@kubernetes/client-node";
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellOpenError, ShellSession } from "../shell-session";
|
||||
import { get, once } from "lodash";
|
||||
import { Node, NodeApi } from "../../../common/k8s-api/endpoints";
|
||||
import { KubeJsonApi } from "../../../common/k8s-api/kube-json-api";
|
||||
import logger from "../../logger";
|
||||
import type { Kubectl } from "../../kubectl/kubectl";
|
||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
|
||||
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
|
||||
}
|
||||
|
||||
export class NodeShellSession extends ShellSession {
|
||||
ShellType = "node-shell";
|
||||
|
||||
protected readonly podName = `node-shell-${uuid()}`;
|
||||
|
||||
protected readonly nodeName: string;
|
||||
protected readonly cwd: string | undefined = undefined;
|
||||
|
||||
constructor(protected nodeName: string, kubectl: Kubectl, socket: WebSocket, cluster: Cluster, terminalId: string) {
|
||||
super(kubectl, socket, cluster, terminalId);
|
||||
constructor(protected readonly dependencies: NodeShellSessionDependencies, { nodeName, ...args }: NodeShellSessionArgs) {
|
||||
super(dependencies, args);
|
||||
this.nodeName = nodeName;
|
||||
}
|
||||
|
||||
public async open() {
|
||||
@@ -35,7 +41,7 @@ export class NodeShellSession extends ShellSession {
|
||||
const cleanup = once(() => {
|
||||
coreApi
|
||||
.deleteNamespacedPod(this.podName, "kube-system")
|
||||
.catch(error => logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
.catch(error => this.dependencies.logger.warn(`[NODE-SHELL]: failed to remove pod shell`, error));
|
||||
});
|
||||
|
||||
this.websocket.once("close", cleanup);
|
||||
@@ -63,7 +69,7 @@ export class NodeShellSession extends ShellSession {
|
||||
const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"];
|
||||
const nodeApi = new NodeApi({
|
||||
objectConstructor: Node,
|
||||
request: KubeJsonApi.forCluster(this.cluster.id),
|
||||
request: this.dependencies.createKubeJsonApiForCluster(this.cluster.id),
|
||||
});
|
||||
const node = await nodeApi.get({ name: this.nodeName });
|
||||
|
||||
@@ -75,7 +81,7 @@ export class NodeShellSession extends ShellSession {
|
||||
|
||||
switch (nodeOs) {
|
||||
default:
|
||||
logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
this.dependencies.logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||
// fallthrough
|
||||
case "linux":
|
||||
args.push("sh", "-c", "((clear && bash) || (clear && ash) || (clear && sh))");
|
||||
@@ -127,7 +133,7 @@ export class NodeShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
protected waitForRunningPod(kc: KubeConfig): Promise<void> {
|
||||
logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
this.dependencies.logger.debug(`[NODE-SHELL]: waiting for ${this.podName} to be running`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new Watch(kc)
|
||||
@@ -146,19 +152,19 @@ export class NodeShellSession extends ShellSession {
|
||||
},
|
||||
// done callback is called if the watch terminates normally
|
||||
(err) => {
|
||||
logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: ${this.podName} was not created in time`);
|
||||
reject(err);
|
||||
},
|
||||
)
|
||||
.then(req => {
|
||||
setTimeout(() => {
|
||||
logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: aborting wait for ${this.podName}, timing out`);
|
||||
req.abort();
|
||||
reject("Pod creation timed out");
|
||||
}, 2 * 60 * 1000); // 2 * 60 * 1000
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
this.dependencies.logger.error(`[NODE-SHELL]: waiting for ${this.podName} failed: ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import type WebSocket from "ws";
|
||||
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
|
||||
import type { NodeShellSessionDependencies } from "./node-shell-session";
|
||||
import { NodeShellSession } from "./node-shell-session";
|
||||
import isMacInjectable from "../../../common/vars/is-mac.injectable";
|
||||
import isWindowsInjectable from "../../../common/vars/is-windows.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import createKubeJsonApiForClusterInjectable from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
nodeName: string;
|
||||
}
|
||||
|
||||
const openNodeShellSessionInjectable = getInjectable({
|
||||
id: "open-node-shell-session",
|
||||
instantiate: (di, params: NodeShellSessionArgs) => {
|
||||
const createKubectl = di.inject(createKubectlInjectable);
|
||||
const dependencies: NodeShellSessionDependencies = {
|
||||
isMac: di.inject(isMacInjectable),
|
||||
isWindows: di.inject(isWindowsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable),
|
||||
};
|
||||
const kubectl = createKubectl(params.cluster.version);
|
||||
const session = new NodeShellSession(dependencies, { kubectl, ...params });
|
||||
|
||||
return session.open();
|
||||
},
|
||||
// NOTE: this must be transient to bypass the circular dependency of `createKubeJsonApiForClusterInjectable` on the lens proxy port
|
||||
lifecycle: lifecycleEnum.transient,
|
||||
});
|
||||
|
||||
export default openNodeShellSessionInjectable;
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { computed } from "mobx";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { isDefined } from "../../../common/utils";
|
||||
import mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
||||
|
||||
export type ModifyTerminalShellEnv = (clusterId: ClusterId, env: Partial<Record<string, string>>) => Partial<Record<string, string>>;
|
||||
|
||||
const modifyTerminalShellEnvInjectable = getInjectable({
|
||||
id: "terminal-shell-env-modify",
|
||||
|
||||
instantiate: (di): ModifyTerminalShellEnv => {
|
||||
const extensions = di.inject(mainExtensionsInjectable);
|
||||
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
|
||||
const terminalShellEnvModifiers = computed(() => (
|
||||
extensions.get()
|
||||
.map((extension) => extension.terminalShellEnvModifier)
|
||||
.filter(isDefined)
|
||||
));
|
||||
|
||||
return (clusterId, env) => {
|
||||
const modifiers = terminalShellEnvModifiers.get();
|
||||
|
||||
if (modifiers.length === 0) {
|
||||
return env;
|
||||
}
|
||||
|
||||
const entity = catalogEntityRegistry.findById(clusterId);
|
||||
|
||||
if (entity) {
|
||||
const ctx = { catalogEntity: entity };
|
||||
|
||||
// clone it so the passed value is not also modified
|
||||
env = JSON.parse(JSON.stringify(env));
|
||||
env = modifiers.reduce((env, modifier) => modifier(ctx, env), env);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default modifyTerminalShellEnvInjectable;
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed } from "mobx";
|
||||
import type { ClusterId } from "../../../common/cluster-types";
|
||||
import { isDefined } from "../../../common/utils";
|
||||
import type { LensMainExtension } from "../../../extensions/lens-main-extension";
|
||||
import type { CatalogEntityRegistry } from "../../catalog";
|
||||
|
||||
interface Dependencies {
|
||||
extensions: IComputedValue<LensMainExtension[]>;
|
||||
catalogEntityRegistry: CatalogEntityRegistry;
|
||||
}
|
||||
|
||||
export const terminalShellEnvModify = ({ extensions, catalogEntityRegistry }: Dependencies) =>
|
||||
(clusterId: ClusterId, env: Record<string, string | undefined>) => {
|
||||
const terminalShellEnvModifiers = computed(() => (
|
||||
extensions.get()
|
||||
.map((extension) => extension.terminalShellEnvModifier)
|
||||
.filter(isDefined)
|
||||
))
|
||||
.get();
|
||||
|
||||
if (terminalShellEnvModifiers.length === 0) {
|
||||
return env;
|
||||
}
|
||||
|
||||
const entity = catalogEntityRegistry.findById(clusterId);
|
||||
|
||||
if (entity) {
|
||||
const ctx = { catalogEntity: entity };
|
||||
|
||||
// clone it so the passed value is not also modified
|
||||
env = JSON.parse(JSON.stringify(env));
|
||||
env = terminalShellEnvModifiers.reduce((env, modifier) => modifier(ctx, env), env);
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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 mainExtensionsInjectable from "../../../extensions/main-extensions.injectable";
|
||||
import { terminalShellEnvModify } from "./terminal-shell-env-modifiers";
|
||||
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
|
||||
|
||||
const terminalShellEnvModifyInjectable = getInjectable({
|
||||
id: "terminal-shell-env-modify",
|
||||
|
||||
instantiate: (di) =>
|
||||
terminalShellEnvModify({
|
||||
extensions: di.inject(mainExtensionsInjectable),
|
||||
catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default terminalShellEnvModifyInjectable;
|
||||
@@ -11,14 +11,13 @@ import { app } from "electron";
|
||||
import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { isMac, isWindows } from "../../common/vars";
|
||||
import { UserStore } from "../../common/user-store";
|
||||
import * as pty from "node-pty";
|
||||
import { appEventBus } from "../../common/app-event-bus/event-bus";
|
||||
import logger from "../logger";
|
||||
import { stat } from "fs/promises";
|
||||
import { getOrInsertWith } from "../../common/utils";
|
||||
import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels";
|
||||
import type { Logger } from "../../common/logger";
|
||||
|
||||
export class ShellOpenError extends Error {
|
||||
constructor(message: string, options?: ErrorOptions) {
|
||||
@@ -104,6 +103,19 @@ export enum WebSocketCloseEvent {
|
||||
TlsHandshake = 1015,
|
||||
}
|
||||
|
||||
export interface ShellSessionDependencies {
|
||||
readonly isWindows: boolean;
|
||||
readonly isMac: boolean;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export interface ShellSessionArgs {
|
||||
kubectl: Kubectl;
|
||||
websocket: WebSocket;
|
||||
cluster: Cluster;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export abstract class ShellSession {
|
||||
abstract readonly ShellType: string;
|
||||
|
||||
@@ -130,6 +142,9 @@ export abstract class ShellSession {
|
||||
protected readonly kubectlBinDirP: Promise<string>;
|
||||
protected readonly kubeconfigPathP: Promise<string>;
|
||||
protected readonly terminalId: string;
|
||||
protected readonly kubectl: Kubectl;
|
||||
protected readonly websocket: WebSocket;
|
||||
protected readonly cluster: Cluster;
|
||||
|
||||
protected abstract get cwd(): string | undefined;
|
||||
|
||||
@@ -147,12 +162,15 @@ export abstract class ShellSession {
|
||||
})
|
||||
));
|
||||
|
||||
logger.info(`[SHELL-SESSION]: PTY for ${this.terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: PTY for ${this.terminalId} is ${resume ? "resumed" : "started"} with PID=${shellProcess.pid}`);
|
||||
|
||||
return { shellProcess, resume };
|
||||
}
|
||||
|
||||
constructor(protected readonly kubectl: Kubectl, protected readonly websocket: WebSocket, protected readonly cluster: Cluster, terminalId: string) {
|
||||
constructor(protected readonly dependencies: ShellSessionDependencies, { kubectl, websocket, cluster, tabId: terminalId }: ShellSessionArgs) {
|
||||
this.kubectl = kubectl;
|
||||
this.websocket = websocket;
|
||||
this.cluster = cluster;
|
||||
this.kubeconfigPathP = this.cluster.getProxyKubeconfigPath();
|
||||
this.kubectlBinDirP = this.kubectl.binDir();
|
||||
this.terminalId = `${cluster.id}:${terminalId}`;
|
||||
@@ -165,7 +183,7 @@ export abstract class ShellSession {
|
||||
protected async getCwd(env: Record<string, string | undefined>): Promise<string> {
|
||||
const cwdOptions = [this.cwd];
|
||||
|
||||
if (isWindows) {
|
||||
if (this.dependencies.isWindows) {
|
||||
cwdOptions.push(
|
||||
env.USERPROFILE,
|
||||
os.homedir(),
|
||||
@@ -177,7 +195,7 @@ export abstract class ShellSession {
|
||||
os.homedir(),
|
||||
);
|
||||
|
||||
if (isMac) {
|
||||
if (this.dependencies.isMac) {
|
||||
cwdOptions.push("/Users");
|
||||
} else {
|
||||
cwdOptions.push("/home");
|
||||
@@ -214,7 +232,7 @@ export abstract class ShellSession {
|
||||
this.running = true;
|
||||
shellProcess.onData(data => this.send({ type: TerminalChannels.STDOUT, data }));
|
||||
shellProcess.onExit(({ exitCode }) => {
|
||||
logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: shell has exited for ${this.terminalId} closed with exitcode=${exitCode}`);
|
||||
|
||||
// This might already be false because of the kill() within the websocket.on("close") handler
|
||||
if (this.running) {
|
||||
@@ -232,11 +250,11 @@ export abstract class ShellSession {
|
||||
this.websocket
|
||||
.on("message", (rawData: unknown): void => {
|
||||
if (!this.running) {
|
||||
return void logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
return void this.dependencies.logger.debug(`[SHELL-SESSION]: received message from ${this.terminalId}, but shellProcess isn't running`);
|
||||
}
|
||||
|
||||
if (!(rawData instanceof Buffer)) {
|
||||
return void logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
return void this.dependencies.logger.error(`[SHELL-SESSION]: Received message non-buffer message.`, { rawData });
|
||||
}
|
||||
|
||||
const data = rawData.toString();
|
||||
@@ -252,18 +270,18 @@ export abstract class ShellSession {
|
||||
shellProcess.resize(message.data.width, message.data.height);
|
||||
break;
|
||||
case TerminalChannels.PING:
|
||||
logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
this.dependencies.logger.silly(`[SHELL-SESSION]: ${this.terminalId} ping!`);
|
||||
break;
|
||||
default:
|
||||
logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: unknown or unhandleable message type for ${this.terminalId}`, message);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.error(`[SHELL-SESSION]: failed to handle message for ${this.terminalId}`, error);
|
||||
}
|
||||
})
|
||||
.once("close", code => {
|
||||
logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: websocket for ${this.terminalId} closed with code=${WebSocketCloseEvent[code]}(${code})`, { cluster: this.cluster.getMeta() });
|
||||
|
||||
const stopShellSession = this.running
|
||||
&& (
|
||||
@@ -278,11 +296,11 @@ export abstract class ShellSession {
|
||||
this.running = false;
|
||||
|
||||
try {
|
||||
logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
this.dependencies.logger.info(`[SHELL-SESSION]: Killing shell process (pid=${shellProcess.pid}) for ${this.terminalId}`);
|
||||
shellProcess.kill();
|
||||
ShellSession.processes.delete(this.terminalId);
|
||||
} catch (error) {
|
||||
logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
this.dependencies.logger.warn(`[SHELL-SESSION]: failed to kill shell process (pid=${shellProcess.pid}) for ${this.terminalId}`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -319,7 +337,7 @@ export abstract class ShellSession {
|
||||
|
||||
delete env.DEBUG; // don't pass DEBUG into shells
|
||||
|
||||
if (isWindows) {
|
||||
if (this.dependencies.isWindows) {
|
||||
env.SystemRoot = process.env.SystemRoot;
|
||||
env.PTYSHELL = shell || "powershell.exe";
|
||||
env.PATH = pathStr;
|
||||
|
||||
Reference in new issue
Block a user