mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix test flakiness because of path side effects, propagate uses to as many places
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
4d2ca3d8b5
commit
156de6138a
@ -351,7 +351,7 @@
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/sharp": "^0.31.0",
|
||||
"@types/spdy": "^3.4.5",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/tar": "^6.1.2",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/tempy": "^0.3.0",
|
||||
|
||||
@ -9,7 +9,6 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { HttpError } from "@kubernetes/client-node";
|
||||
import type { Kubectl } from "../../main/kubectl/kubectl";
|
||||
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
||||
import { loadConfigFromFile } from "../kube-helpers";
|
||||
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||
import { apiResourceRecord, apiResources } from "../rbac";
|
||||
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||
@ -25,6 +24,7 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
|
||||
import assert from "assert";
|
||||
import type { Logger } from "../logger";
|
||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||
|
||||
export interface ClusterDependencies {
|
||||
readonly directoryForKubeConfigs: string;
|
||||
@ -37,6 +37,7 @@ export interface ClusterDependencies {
|
||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||
broadcastMessage: BroadcastMessage;
|
||||
loadConfigfromFile: LoadConfigfromFile;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -500,7 +501,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
}
|
||||
|
||||
async getKubeconfig(): Promise<KubeConfig> {
|
||||
const { config } = await loadConfigFromFile(this.kubeConfigPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
@ -510,7 +511,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
*/
|
||||
async getProxyKubeconfig(): Promise<KubeConfig> {
|
||||
const proxyKCPath = await this.getProxyKubeconfigPath();
|
||||
const { config } = await loadConfigFromFile(proxyKCPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
11
src/common/fs/extract-tar.global-override-for-injectable.ts
Normal file
11
src/common/fs/extract-tar.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import extractTarInjectable from "./extract-tar.injectable";
|
||||
|
||||
export default getGlobalOverride(extractTarInjectable, () => async () => {
|
||||
throw new Error("tried to extract a tar file without override");
|
||||
});
|
||||
26
src/common/fs/extract-tar.injectable.ts
Normal file
26
src/common/fs/extract-tar.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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 { ExtractOptions } from "tar";
|
||||
import { extract } from "tar";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
|
||||
export type ExtractTar = (filePath: string, opts?: ExtractOptions) => Promise<void>;
|
||||
|
||||
const extractTarInjectable = getInjectable({
|
||||
id: "extract-tar",
|
||||
instantiate: (di): ExtractTar => {
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return (filePath, opts = {}) => extract({
|
||||
file: filePath,
|
||||
cwd: getDirnameOfPath(filePath),
|
||||
...opts,
|
||||
});
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default extractTarInjectable;
|
||||
11
src/common/fs/move.global-override-for-injectable.ts
Normal file
11
src/common/fs/move.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import moveInjectable from "./move.injectable";
|
||||
|
||||
export default getGlobalOverride(moveInjectable, () => async () => {
|
||||
throw new Error("tried to move without override");
|
||||
});
|
||||
16
src/common/fs/move.injectable.ts
Normal file
16
src/common/fs/move.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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 { MoveOptions } from "fs-extra";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Move = (src: string, dest: string, options?: MoveOptions) => Promise<void>;
|
||||
|
||||
const moveInjectable = getInjectable({
|
||||
id: "move",
|
||||
instantiate: (di): Move => di.inject(fsInjectable).move,
|
||||
});
|
||||
|
||||
export default moveInjectable;
|
||||
@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import readDirInjectable from "./read-dir.injectable";
|
||||
import readDirectoryInjectable from "./read-directory.injectable";
|
||||
|
||||
export default getGlobalOverride(readDirInjectable, () => async () => {
|
||||
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
|
||||
throw new Error("tried to read a directory's content without override");
|
||||
});
|
||||
@ -29,9 +29,9 @@ export interface ReadDirectory {
|
||||
): Promise<Dirent[]>;
|
||||
}
|
||||
|
||||
const readDirInjectable = getInjectable({
|
||||
id: "read-dir",
|
||||
const readDirectoryInjectable = getInjectable({
|
||||
id: "read-directory",
|
||||
instantiate: (di): ReadDirectory => di.inject(fsInjectable).readdir,
|
||||
});
|
||||
|
||||
export default readDirInjectable;
|
||||
export default readDirectoryInjectable;
|
||||
11
src/common/fs/remove-path.global-override-for-injectable.ts
Normal file
11
src/common/fs/remove-path.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import removePathInjectable from "./remove-path.injectable";
|
||||
|
||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
||||
throw new Error("tried to remove a path without override");
|
||||
});
|
||||
15
src/common/fs/remove-path.injectable.ts
Normal file
15
src/common/fs/remove-path.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 fsInjectable from "./fs.injectable";
|
||||
|
||||
export type RemovePath = (path: string) => Promise<void>;
|
||||
|
||||
const removePathInjectable = getInjectable({
|
||||
id: "remove-path",
|
||||
instantiate: (di): RemovePath => di.inject(fsInjectable).remove,
|
||||
});
|
||||
|
||||
export default removePathInjectable;
|
||||
@ -3,7 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
const writeFileInjectable = getInjectable({
|
||||
@ -11,9 +11,10 @@ const writeFileInjectable = getInjectable({
|
||||
|
||||
instantiate: (di) => {
|
||||
const { writeFile, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return async (filePath: string, content: string | Buffer) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
await ensureDir(getDirnameOfPath(filePath), { mode: 0o755 });
|
||||
|
||||
await writeFile(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
|
||||
@ -3,37 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { EnsureOptions, WriteOptions } from "fs-extra";
|
||||
import path from "path";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
|
||||
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
|
||||
}
|
||||
|
||||
const writeJsonFile = ({ writeJson, ensureDir }: Dependencies): WriteJson => async (filePath, content) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
|
||||
await writeJson(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
spaces: 2,
|
||||
});
|
||||
};
|
||||
|
||||
const writeJsonFileInjectable = getInjectable({
|
||||
id: "write-json-file",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): WriteJson => {
|
||||
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return writeJsonFile({
|
||||
writeJson,
|
||||
ensureDir,
|
||||
});
|
||||
return async (filePath, content) => {
|
||||
await ensureDir(getDirnameOfPath(filePath), { mode: 0o755 });
|
||||
|
||||
await writeJson(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
spaces: 2,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -4,31 +4,14 @@
|
||||
*/
|
||||
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import fse from "fs-extra";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import yaml from "js-yaml";
|
||||
import logger from "../main/logger";
|
||||
import type { Cluster, Context, User } from "@kubernetes/client-node/dist/config_types";
|
||||
import { newClusters, newContexts, newUsers } from "@kubernetes/client-node/dist/config_types";
|
||||
import { isDefined, resolvePath } from "./utils";
|
||||
import { isDefined } from "./utils";
|
||||
import Joi from "joi";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
|
||||
export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config");
|
||||
|
||||
export function loadConfigFromFileSync(filePath: string): ConfigResult {
|
||||
const content = fse.readFileSync(resolvePath(filePath), "utf-8");
|
||||
|
||||
return loadConfigFromString(content);
|
||||
}
|
||||
|
||||
export async function loadConfigFromFile(filePath: string): Promise<ConfigResult> {
|
||||
const content = await fse.readFile(resolvePath(filePath), "utf-8");
|
||||
|
||||
return loadConfigFromString(content);
|
||||
}
|
||||
|
||||
const clusterSchema = Joi.object({
|
||||
name: Joi
|
||||
.string()
|
||||
|
||||
23
src/common/kube-helpers/load-config-from-file.injectable.ts
Normal file
23
src/common/kube-helpers/load-config-from-file.injectable.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 readFileInjectable from "../fs/read-file.injectable";
|
||||
import type { ConfigResult } from "../kube-helpers";
|
||||
import { loadConfigFromString } from "../kube-helpers";
|
||||
import resolveTildeInjectable from "../path/resolve-tilde.injectable";
|
||||
|
||||
export type LoadConfigfromFile = (filePath: string) => Promise<ConfigResult>;
|
||||
|
||||
const loadConfigfromFileInjectable = getInjectable({
|
||||
id: "load-configfrom-file",
|
||||
instantiate: (di): LoadConfigfromFile => {
|
||||
const readFile = di.inject(readFileInjectable);
|
||||
const resolveTilde = di.inject(resolveTildeInjectable);
|
||||
|
||||
return async (filePath) => loadConfigFromString(await readFile(resolveTilde(filePath)));
|
||||
},
|
||||
});
|
||||
|
||||
export default loadConfigfromFileInjectable;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import homeDirectoryPathInjectable from "./home-directory-path.injectable";
|
||||
|
||||
export default getGlobalOverride(homeDirectoryPathInjectable, () => "/some-home-directory");
|
||||
14
src/common/os/home-directory-path.injectable.ts
Normal file
14
src/common/os/home-directory-path.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { homedir } from "os";
|
||||
|
||||
const homeDirectoryPathInjectable = getInjectable({
|
||||
id: "home-directory-path",
|
||||
instantiate: () => homedir(),
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default homeDirectoryPathInjectable;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import tempDirectoryPathInjectable from "./temp-directory-path.injectable";
|
||||
|
||||
export default getGlobalOverride(tempDirectoryPathInjectable, () => "/some-temp-directory");
|
||||
14
src/common/os/temp-directory-path.injectable.ts
Normal file
14
src/common/os/temp-directory-path.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { tmpdir } from "os";
|
||||
|
||||
const tempDirectoryPathInjectable = getInjectable({
|
||||
id: "temp-directory-path",
|
||||
instantiate: () => tmpdir(),
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default tempDirectoryPathInjectable;
|
||||
51
src/common/path/is-logical-child-path.injectable.ts
Normal file
51
src/common/path/is-logical-child-path.injectable.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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 getAbsolutePathInjectable from "./get-absolute-path.injectable";
|
||||
import getDirnameOfPathInjectable from "./get-dirname.injectable";
|
||||
|
||||
/**
|
||||
* Checks if `testPath` represents a potential filesystem entry that would be
|
||||
* logically "within" the `parentPath` directory.
|
||||
*
|
||||
* This function will return `true` in the above case, and `false` otherwise.
|
||||
* It will return `false` if the two paths are the same (after resolving them).
|
||||
*
|
||||
* The function makes no FS calls and is platform dependant. Meaning that the
|
||||
* results are only guaranteed to be correct for the platform you are running
|
||||
* on.
|
||||
* @param parentPath The known path of a directory
|
||||
* @param testPath The path that is to be tested
|
||||
*/
|
||||
export type IsLogicalChildPath = (parentPath: string, testPath: string) => boolean;
|
||||
|
||||
const isLogicalChildPathInjectable = getInjectable({
|
||||
id: "is-logical-child-path",
|
||||
instantiate: (di): IsLogicalChildPath => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return (parentPath, testPath) => {
|
||||
const resolvedParentPath = getAbsolutePath(parentPath);
|
||||
let resolvedTestPath = getAbsolutePath(testPath);
|
||||
|
||||
if (resolvedParentPath === resolvedTestPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (resolvedTestPath.length >= resolvedParentPath.length) {
|
||||
if (resolvedTestPath === resolvedParentPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
resolvedTestPath = getDirnameOfPath(resolvedTestPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default isLogicalChildPathInjectable;
|
||||
10
src/common/path/parse.global-override-for-injectable.ts
Normal file
10
src/common/path/parse.global-override-for-injectable.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import parsePathInjectable from "./parse.injectable";
|
||||
|
||||
export default getGlobalOverride(parsePathInjectable, () => path.posix.parse);
|
||||
14
src/common/path/parse.injectable.ts
Normal file
14
src/common/path/parse.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 path from "path";
|
||||
|
||||
const parsePathInjectable = getInjectable({
|
||||
id: "parse-path",
|
||||
instantiate: () => path.parse,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default parsePathInjectable;
|
||||
21
src/common/path/resolve-path.injectable.ts
Normal file
21
src/common/path/resolve-path.injectable.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 getAbsolutePathInjectable from "./get-absolute-path.injectable";
|
||||
import resolveTildeInjectable from "./resolve-tilde.injectable";
|
||||
|
||||
export type ResolvePath = (path: string) => string;
|
||||
|
||||
const resolvePathInjectable = getInjectable({
|
||||
id: "resolve-path",
|
||||
instantiate: (di): ResolvePath => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const resolveTilde = di.inject(resolveTildeInjectable);
|
||||
|
||||
return (filePath) => getAbsolutePath(resolveTilde(filePath));
|
||||
},
|
||||
});
|
||||
|
||||
export default resolvePathInjectable;
|
||||
31
src/common/path/resolve-tilde.injectable.ts
Normal file
31
src/common/path/resolve-tilde.injectable.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 homeDirectoryPathInjectable from "../os/home-directory-path.injectable";
|
||||
import fileSystemSeparatorInjectable from "./separator.injectable";
|
||||
|
||||
export type ResolveTilde = (path: string) => string;
|
||||
|
||||
const resolveTildeInjectable = getInjectable({
|
||||
id: "resolve-tilde",
|
||||
instantiate: (di): ResolveTilde => {
|
||||
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
|
||||
const fileSystemSeparator = di.inject(fileSystemSeparatorInjectable);
|
||||
|
||||
return (filePath) => {
|
||||
if (filePath === "~") {
|
||||
return homeDirectoryPath;
|
||||
}
|
||||
|
||||
if (filePath === `~${fileSystemSeparator}`) {
|
||||
return `${homeDirectoryPath}${filePath.slice(1)}`;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default resolveTildeInjectable;
|
||||
@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import fse from "fs-extra";
|
||||
import path from "path";
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isErrnoException } from "../utils";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||
|
||||
export type UserStoreFileNameMigration = () => Promise<void>;
|
||||
|
||||
@ -15,8 +15,9 @@ const userStoreFileNameMigrationInjectable = getInjectable({
|
||||
id: "user-store-file-name-migration",
|
||||
instantiate: (di): UserStoreFileNameMigration => {
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const configJsonPath = path.join(userDataPath, "config.json");
|
||||
const lensUserStoreJsonPath = path.join(userDataPath, "lens-user-store.json");
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const configJsonPath = joinPaths(userDataPath, "config.json");
|
||||
const lensUserStoreJsonPath = joinPaths(userDataPath, "lens-user-store.json");
|
||||
|
||||
return async () => {
|
||||
try {
|
||||
|
||||
@ -7,7 +7,6 @@ import { app } from "electron";
|
||||
import { action, computed, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx";
|
||||
import { BaseStore } from "../base-store";
|
||||
import migrations from "../../migrations/user-store";
|
||||
import { kubeConfigDefaultPath } from "../kube-helpers";
|
||||
import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
||||
import { DESCRIPTORS } from "./preferences-helpers";
|
||||
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
|
||||
@ -38,12 +37,6 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
||||
|
||||
@observable lastSeenAppVersion = "0.0.0";
|
||||
|
||||
/**
|
||||
* used in add-cluster page for providing context
|
||||
* @deprecated No longer used
|
||||
*/
|
||||
@observable kubeConfigPath = kubeConfigDefaultPath;
|
||||
|
||||
/**
|
||||
* @deprecated No longer used
|
||||
*/
|
||||
|
||||
@ -3,12 +3,29 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { describeIf } from "../../../../integration/helpers/utils";
|
||||
import { isWindows } from "../../vars";
|
||||
import { isLogicalChildPath } from "../paths";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import getDirnameOfPathInjectable from "../../path/get-dirname.injectable";
|
||||
import type { IsLogicalChildPath } from "../../path/is-logical-child-path.injectable";
|
||||
import isLogicalChildPathInjectable from "../../path/is-logical-child-path.injectable";
|
||||
|
||||
describe("isLogicalChildPath", () => {
|
||||
describeIf(isWindows)("windows tests", () => {
|
||||
let di: DiContainer;
|
||||
let isLogicalChildPath: IsLogicalChildPath;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting();
|
||||
});
|
||||
|
||||
describe("when using win32 paths", () => {
|
||||
beforeEach(() => {
|
||||
di.override(getAbsolutePathInjectable, () => path.win32.resolve);
|
||||
di.override(getDirnameOfPathInjectable, () => path.win32.dirname);
|
||||
isLogicalChildPath = di.inject(isLogicalChildPathInjectable);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
parentPath: "C:\\Foo",
|
||||
@ -40,7 +57,13 @@ describe("isLogicalChildPath", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describeIf(!isWindows)("posix tests", () => {
|
||||
describe("when using posix paths", () => {
|
||||
beforeEach(() => {
|
||||
di.override(getAbsolutePathInjectable, () => path.posix.resolve);
|
||||
di.override(getDirnameOfPathInjectable, () => path.posix.dirname);
|
||||
isLogicalChildPath = di.inject(isLogicalChildPathInjectable);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
parentPath: "/foo",
|
||||
|
||||
@ -22,7 +22,6 @@ export * from "./hash-set";
|
||||
export * from "./n-fircate";
|
||||
export * from "./noop";
|
||||
export * from "./observable-crate/impl";
|
||||
export * from "./paths";
|
||||
export * from "./promise-exec";
|
||||
export * from "./readonly";
|
||||
export * from "./reject-promise";
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
export function resolveTilde(filePath: string) {
|
||||
if (filePath === "~") {
|
||||
return os.homedir();
|
||||
}
|
||||
|
||||
if (filePath.startsWith("~/")) {
|
||||
return `${os.homedir()}${filePath.slice(1)}`;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export function resolvePath(filePath: string): string {
|
||||
return path.resolve(resolveTilde(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if `testPath` represents a potential filesystem entry that would be
|
||||
* logically "within" the `parentPath` directory.
|
||||
*
|
||||
* This function will return `true` in the above case, and `false` otherwise.
|
||||
* It will return `false` if the two paths are the same (after resolving them).
|
||||
*
|
||||
* The function makes no FS calls and is platform dependant. Meaning that the
|
||||
* results are only guaranteed to be correct for the platform you are running
|
||||
* on.
|
||||
* @param parentPath The known path of a directory
|
||||
* @param testPath The path that is to be tested
|
||||
*/
|
||||
export function isLogicalChildPath(parentPath: string, testPath: string): boolean {
|
||||
parentPath = path.resolve(parentPath);
|
||||
testPath = path.resolve(testPath);
|
||||
|
||||
if (parentPath === testPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (testPath.length >= parentPath.length) {
|
||||
if (testPath === parentPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
testPath = path.dirname(testPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
// Helper for working with tarball files (.tar, .tgz)
|
||||
// Docs: https://github.com/npm/node-tar
|
||||
import type { ExtractOptions, FileStat } from "tar";
|
||||
import type { FileStat } from "tar";
|
||||
import tar from "tar";
|
||||
import path from "path";
|
||||
import { parse } from "./json";
|
||||
@ -62,18 +62,10 @@ export async function listTarEntries(filePath: string): Promise<string[]> {
|
||||
|
||||
await tar.list({
|
||||
file: filePath,
|
||||
onentry: (entry: FileStat) => {
|
||||
onentry: (entry) => {
|
||||
entries.push(path.normalize(entry.path as unknown as string));
|
||||
},
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function extractTar(filePath: string, opts: ExtractOptions & { sync?: boolean } = {}) {
|
||||
return tar.extract({
|
||||
file: filePath,
|
||||
cwd: path.dirname(filePath),
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
@ -18,16 +18,16 @@ import pathExistsInjectable from "../../common/fs/path-exists.injectable";
|
||||
import watchInjectable from "../../common/fs/watch/watch.injectable";
|
||||
import accessPathInjectable from "../../common/fs/access-path.injectable";
|
||||
import copyInjectable from "../../common/fs/copy.injectable";
|
||||
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
|
||||
import ensureDirInjectable from "../../common/fs/ensure-dir.injectable";
|
||||
import isProductionInjectable from "../../common/vars/is-production.injectable";
|
||||
import lstatInjectable from "../../common/fs/lstat.injectable";
|
||||
import readDirInjectable from "../../common/fs/read-dir.injectable";
|
||||
import readDirectoryInjectable from "../../common/fs/read-directory.injectable";
|
||||
import fileSystemSeparatorInjectable from "../../common/path/separator.injectable";
|
||||
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
import getRelativePathInjectable from "../../common/path/get-relative-path.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import removePathInjectable from "../../common/fs/remove-path.injectable";
|
||||
|
||||
const extensionDiscoveryInjectable = getInjectable({
|
||||
id: "extension-discovery",
|
||||
@ -47,11 +47,11 @@ const extensionDiscoveryInjectable = getInjectable({
|
||||
logger: di.inject(loggerInjectable),
|
||||
accessPath: di.inject(accessPathInjectable),
|
||||
copy: di.inject(copyInjectable),
|
||||
deleteFile: di.inject(deleteFileInjectable),
|
||||
removePath: di.inject(removePathInjectable),
|
||||
ensureDirectory: di.inject(ensureDirInjectable),
|
||||
isProduction: di.inject(isProductionInjectable),
|
||||
lstat: di.inject(lstatInjectable),
|
||||
readDirectory: di.inject(readDirInjectable),
|
||||
readDirectory: di.inject(readDirectoryInjectable),
|
||||
fileSystemSeparator: di.inject(fileSystemSeparatorInjectable),
|
||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
|
||||
@ -22,9 +22,8 @@ import type { PathExists } from "../../common/fs/path-exists.injectable";
|
||||
import type { Watch } from "../../common/fs/watch/watch.injectable";
|
||||
import type { Stats } from "fs";
|
||||
import { constants } from "fs";
|
||||
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
|
||||
import type { LStat } from "../../common/fs/lstat.injectable";
|
||||
import type { ReadDirectory } from "../../common/fs/read-dir.injectable";
|
||||
import type { ReadDirectory } from "../../common/fs/read-directory.injectable";
|
||||
import type { EnsureDirectory } from "../../common/fs/ensure-dir.injectable";
|
||||
import type { AccessPath } from "../../common/fs/access-path.injectable";
|
||||
import type { Copy } from "../../common/fs/copy.injectable";
|
||||
@ -32,6 +31,7 @@ import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||
import type { GetRelativePath } from "../../common/path/get-relative-path.injectable";
|
||||
import type { RemovePath } from "../../common/fs/remove-path.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
readonly extensionLoader: ExtensionLoader;
|
||||
@ -47,7 +47,7 @@ interface Dependencies {
|
||||
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
|
||||
readJsonFile: ReadJson;
|
||||
pathExists: PathExists;
|
||||
deleteFile: DeleteFile;
|
||||
removePath: RemovePath;
|
||||
lstat: LStat;
|
||||
watch: Watch;
|
||||
readDirectory: ReadDirectory;
|
||||
@ -222,7 +222,7 @@ export class ExtensionDiscovery {
|
||||
|
||||
if (extension) {
|
||||
// Remove a broken symlink left by a previous installation if it exists.
|
||||
await this.dependencies.deleteFile(extension.manifestPath);
|
||||
await this.dependencies.removePath(extension.manifestPath);
|
||||
|
||||
// Install dependencies for the new extension
|
||||
await this.dependencies.installExtension(extension.absolutePath);
|
||||
@ -285,7 +285,7 @@ export class ExtensionDiscovery {
|
||||
* @param name e.g. "@mirantis/lens-extension-cc"
|
||||
*/
|
||||
removeSymlinkByPackageName(name: string): Promise<void> {
|
||||
return this.dependencies.deleteFile(this.getInstalledPath(name));
|
||||
return this.dependencies.removePath(this.getInstalledPath(name));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,7 +307,7 @@ export class ExtensionDiscovery {
|
||||
await this.removeSymlinkByPackageName(manifest.name);
|
||||
|
||||
// fs.remove does nothing if the path doesn't exist anymore
|
||||
await this.dependencies.deleteFile(absolutePath);
|
||||
await this.dependencies.removePath(absolutePath);
|
||||
}
|
||||
|
||||
async load(): Promise<Map<LensExtensionId, InstalledExtension>> {
|
||||
@ -322,7 +322,7 @@ export class ExtensionDiscovery {
|
||||
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
|
||||
);
|
||||
|
||||
await this.dependencies.deleteFile(this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "package-lock.json"));
|
||||
await this.dependencies.removePath(this.dependencies.joinPaths(this.dependencies.extensionPackageRootDirectory, "package-lock.json"));
|
||||
|
||||
const canWriteToInTreeFolder = await this.dependencies.accessPath(this.inTreeFolderPath, constants.W_OK);
|
||||
|
||||
@ -331,7 +331,7 @@ export class ExtensionDiscovery {
|
||||
this.bundledFolderPath = this.inTreeFolderPath;
|
||||
} else {
|
||||
// Remove e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||
await this.dependencies.deleteFile(this.inTreeTargetPath);
|
||||
await this.dependencies.removePath(this.inTreeTargetPath);
|
||||
|
||||
// Create folder e.g. /Users/<username>/Library/Application Support/LensDev/extensions
|
||||
await this.dependencies.ensureDirectory(this.inTreeTargetPath);
|
||||
|
||||
@ -10,6 +10,8 @@ import extensionInstancesInjectable from "./extension-instances.injectable";
|
||||
import type { LensExtension } from "../lens-extension";
|
||||
import extensionInjectable from "./extension/extension.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
|
||||
const extensionLoaderInjectable = getInjectable({
|
||||
id: "extension-loader",
|
||||
@ -20,6 +22,8 @@ const extensionLoaderInjectable = getInjectable({
|
||||
extensionInstances: di.inject(extensionInstancesInjectable),
|
||||
getExtension: (instance: LensExtension) => di.inject(extensionInjectable, instance),
|
||||
logger: di.inject(loggerInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import { ipcMain, ipcRenderer } from "electron";
|
||||
import { isEqual } from "lodash";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
||||
import path from "path";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
|
||||
import type { Disposer } from "../../common/utils";
|
||||
import { isDefined, toJS } from "../../common/utils";
|
||||
@ -23,6 +22,8 @@ import { EventEmitter } from "../../common/event-emitter";
|
||||
import type { CreateExtensionInstance } from "./create-extension-instance.token";
|
||||
import type { Extension } from "./extension/extension.injectable";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||
|
||||
const logModule = "[EXTENSIONS-LOADER]";
|
||||
|
||||
@ -32,6 +33,8 @@ interface Dependencies {
|
||||
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void;
|
||||
createExtensionInstance: CreateExtensionInstance;
|
||||
getExtension: (instance: LensExtension) => Extension;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
}
|
||||
|
||||
export interface ExtensionLoading {
|
||||
@ -371,7 +374,7 @@ export class ExtensionLoader {
|
||||
return null;
|
||||
}
|
||||
|
||||
const extAbsolutePath = path.resolve(path.join(path.dirname(extension.manifestPath), extRelativePath));
|
||||
const extAbsolutePath = this.dependencies.joinPaths(this.dependencies.getDirnameOfPath(extension.manifestPath), extRelativePath);
|
||||
|
||||
try {
|
||||
return __non_webpack_require__(extAbsolutePath).default;
|
||||
|
||||
@ -16,6 +16,7 @@ import loggerInjectable from "../../common/logger.injectable";
|
||||
import detectorRegistryInjectable from "../cluster-detectors/detector-registry.injectable";
|
||||
import createVersionDetectorInjectable from "../cluster-detectors/create-version-detector.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable";
|
||||
|
||||
const createClusterInjectable = getInjectable({
|
||||
id: "create-cluster",
|
||||
@ -32,6 +33,7 @@ const createClusterInjectable = getInjectable({
|
||||
detectorRegistry: di.inject(detectorRegistryInjectable),
|
||||
createVersionDetector: di.inject(createVersionDetectorInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
||||
};
|
||||
|
||||
return (model, configData) => new Cluster(dependencies, model, configData);
|
||||
|
||||
@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import path from "path";
|
||||
import selfsigned from "selfsigned";
|
||||
import { getBinaryName } from "../../common/vars";
|
||||
import spawnInjectable from "../child-process/spawn.injectable";
|
||||
@ -14,6 +13,7 @@ import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||
|
||||
@ -22,11 +22,12 @@ const createKubeAuthProxyInjectable = getInjectable({
|
||||
|
||||
instantiate: (di): CreateKubeAuthProxy => {
|
||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||
const clusterUrl = new URL(cluster.apiUrl);
|
||||
const dependencies: KubeAuthProxyDependencies = {
|
||||
proxyBinPath: path.join(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
||||
proxyBinPath: joinPaths(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
||||
spawn: di.inject(spawnInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
|
||||
@ -9,6 +9,8 @@ import type { KubeconfigManagerDependencies } from "./kubeconfig-manager";
|
||||
import { KubeconfigManager } from "./kubeconfig-manager";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
|
||||
export interface KubeConfigManagerInstantiationParameter {
|
||||
cluster: Cluster;
|
||||
@ -24,6 +26,8 @@ const createKubeconfigManagerInjectable = getInjectable({
|
||||
directoryForTemp: di.inject(directoryForTempInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
lensProxyPort: di.inject(lensProxyPortInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
};
|
||||
|
||||
return (cluster) => new KubeconfigManager(dependencies, cluster);
|
||||
|
||||
@ -6,17 +6,20 @@
|
||||
import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import type { Cluster } from "../../common/cluster/cluster";
|
||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import { dumpConfigYaml } from "../../common/kube-helpers";
|
||||
import { isErrnoException } from "../../common/utils";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||
|
||||
export interface KubeconfigManagerDependencies {
|
||||
readonly directoryForTemp: string;
|
||||
readonly logger: Logger;
|
||||
lensProxyPort: { get: () => number };
|
||||
readonly lensProxyPort: { get: () => number };
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
}
|
||||
|
||||
export class KubeconfigManager {
|
||||
@ -93,7 +96,7 @@ export class KubeconfigManager {
|
||||
protected async createProxyKubeconfig(): Promise<string> {
|
||||
const { cluster } = this;
|
||||
const { contextName, id } = cluster;
|
||||
const tempFile = path.join(
|
||||
const tempFile = this.dependencies.joinPaths(
|
||||
this.dependencies.directoryForTemp,
|
||||
`kubeconfig-${id}`,
|
||||
);
|
||||
@ -121,7 +124,7 @@ export class KubeconfigManager {
|
||||
// write
|
||||
const configYaml = dumpConfigYaml(proxyConfig);
|
||||
|
||||
await fs.ensureDir(path.dirname(tempFile));
|
||||
await fs.ensureDir(this.dependencies.getDirnameOfPath(tempFile));
|
||||
await fs.writeFile(tempFile, configYaml, { mode: 0o600 });
|
||||
this.dependencies.logger.debug(`[KUBECONFIG-MANAGER]: Created temp kubeconfig "${contextName}" at "${tempFile}": \n${configYaml}`);
|
||||
|
||||
|
||||
@ -3,16 +3,20 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import kubectlBinaryNameInjectable from "./binary-name.injectable";
|
||||
|
||||
const bundledKubectlBinaryPathInjectable = getInjectable({
|
||||
id: "bundled-kubectl-binary-path",
|
||||
instantiate: (di) => path.join(
|
||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
di.inject(kubectlBinaryNameInjectable),
|
||||
),
|
||||
instantiate: (di) => {
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return joinPaths(
|
||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
di.inject(kubectlBinaryNameInjectable),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default bundledKubectlBinaryPathInjectable;
|
||||
|
||||
@ -14,6 +14,9 @@ import bundledKubectlBinaryPathInjectable from "./bundled-binary-path.injectable
|
||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
||||
import bundledKubectlVersionInjectable from "../../common/vars/bundled-kubectl-version.injectable";
|
||||
import kubectlVersionMapInjectable from "./version-map.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable";
|
||||
|
||||
const createKubectlInjectable = getInjectable({
|
||||
id: "create-kubectl",
|
||||
@ -29,6 +32,9 @@ const createKubectlInjectable = getInjectable({
|
||||
baseBundeledBinariesDirectory: di.inject(baseBundledBinariesDirectoryInjectable),
|
||||
bundledKubectlVersion: di.inject(bundledKubectlVersionInjectable),
|
||||
kubectlVersionMap: di.inject(kubectlVersionMapInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
joinPaths: di.inject(joinPathsInjectable),
|
||||
getBasenameOfPath: di.inject(getBasenameOfPathInjectable),
|
||||
};
|
||||
|
||||
return (clusterVersion: string) => new Kubectl(dependencies, clusterVersion);
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||
import logger from "../logger";
|
||||
@ -15,6 +14,9 @@ import got from "got/dist/source";
|
||||
import { promisify } from "util";
|
||||
import stream from "stream";
|
||||
import { noop } from "lodash/fp";
|
||||
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";
|
||||
|
||||
const initScriptVersionString = "# lens-initscript v3";
|
||||
|
||||
@ -33,6 +35,9 @@ export interface KubectlDependencies {
|
||||
};
|
||||
readonly bundledKubectlVersion: string;
|
||||
readonly kubectlVersionMap: Map<string, string>;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
getBasenameOfPath: GetBasenameOfPath;
|
||||
}
|
||||
|
||||
export class Kubectl {
|
||||
@ -71,8 +76,8 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${this.dependencies.normalizedDownloadPlatform}/${this.dependencies.normalizedDownloadArch}/${this.dependencies.kubectlBinaryName}`;
|
||||
this.dirname = path.normalize(path.join(this.getDownloadDir(), this.kubectlVersion));
|
||||
this.path = path.join(this.dirname, this.dependencies.kubectlBinaryName);
|
||||
this.dirname = this.dependencies.joinPaths(this.getDownloadDir(), this.kubectlVersion);
|
||||
this.path = this.dependencies.joinPaths(this.dirname, this.dependencies.kubectlBinaryName);
|
||||
}
|
||||
|
||||
public getBundledPath() {
|
||||
@ -85,7 +90,7 @@ export class Kubectl {
|
||||
|
||||
protected getDownloadDir() {
|
||||
if (this.dependencies.userStore.downloadBinariesPath) {
|
||||
return path.join(this.dependencies.userStore.downloadBinariesPath, "kubectl");
|
||||
return this.dependencies.joinPaths(this.dependencies.userStore.downloadBinariesPath, "kubectl");
|
||||
}
|
||||
|
||||
return this.dependencies.directoryForKubectlBinaries;
|
||||
@ -104,7 +109,7 @@ export class Kubectl {
|
||||
if (!await this.checkBinary(this.getBundledPath(), false)) {
|
||||
Kubectl.invalidBundle = true;
|
||||
|
||||
return path.basename(this.getBundledPath());
|
||||
return this.dependencies.getBasenameOfPath(this.getBundledPath());
|
||||
}
|
||||
|
||||
try {
|
||||
@ -246,7 +251,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
public async downloadKubectl() {
|
||||
await ensureDir(path.dirname(this.path), 0o755);
|
||||
await ensureDir(this.dependencies.getDirnameOfPath(this.path), 0o755);
|
||||
|
||||
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
|
||||
|
||||
@ -268,9 +273,9 @@ export class Kubectl {
|
||||
const binariesDir = this.dependencies.baseBundeledBinariesDirectory;
|
||||
const kubectlPath = this.dependencies.userStore.downloadKubectlBinaries
|
||||
? this.dirname
|
||||
: path.dirname(this.getPathFromPreferences());
|
||||
: this.dependencies.getDirnameOfPath(this.getPathFromPreferences());
|
||||
|
||||
const bashScriptPath = path.join(this.dirname, ".bash_set_path");
|
||||
const bashScriptPath = this.dependencies.joinPaths(this.dirname, ".bash_set_path");
|
||||
const bashScript = [
|
||||
initScriptVersionString,
|
||||
"tempkubeconfig=\"$KUBECONFIG\"",
|
||||
@ -292,7 +297,7 @@ export class Kubectl {
|
||||
"unset tempkubeconfig",
|
||||
].join("\n");
|
||||
|
||||
const zshScriptPath = path.join(this.dirname, ".zlogin");
|
||||
const zshScriptPath = this.dependencies.joinPaths(this.dirname, ".zlogin");
|
||||
const zshScript = [
|
||||
initScriptVersionString,
|
||||
"tempkubeconfig=\"$KUBECONFIG\"",
|
||||
|
||||
@ -3,16 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import type { UserStore } from "../../../common/user-store";
|
||||
import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session";
|
||||
import { ShellSession } from "../shell-session";
|
||||
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 {
|
||||
modifyTerminalShellEnv: ModifyTerminalShellEnv;
|
||||
readonly directoryForBinaries: string;
|
||||
readonly userStore: UserStore;
|
||||
modifyTerminalShellEnv: ModifyTerminalShellEnv;
|
||||
joinPaths: JoinPaths;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
getBasenameOfPath: GetBasenameOfPath;
|
||||
}
|
||||
|
||||
export class LocalShellSession extends ShellSession {
|
||||
@ -49,13 +54,15 @@ export class LocalShellSession extends ShellSession {
|
||||
|
||||
protected async getShellArgs(shell: string): Promise<string[]> {
|
||||
const pathFromPreferences = this.dependencies.userStore.kubectlBinariesPath || this.kubectl.getBundledPath();
|
||||
const kubectlPathDir = this.dependencies.userStore.downloadKubectlBinaries ? await this.kubectlBinDirP : path.dirname(pathFromPreferences);
|
||||
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};${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}:${this.dependencies.directoryForBinaries}:$PATH"; export KUBECONFIG="${await this.kubeconfigPathP}"`];
|
||||
case "zsh":
|
||||
|
||||
@ -14,6 +14,9 @@ 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;
|
||||
@ -35,6 +38,9 @@ const openLocalShellSessionInjectable = getInjectable({
|
||||
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) => {
|
||||
|
||||
@ -6,9 +6,8 @@
|
||||
// Move embedded kubeconfig into separate file and add reference to it to cluster settings
|
||||
// convert file path cluster icons to their base64 encoded versions
|
||||
|
||||
import path from "path";
|
||||
import fse from "fs-extra";
|
||||
import { loadConfigFromFileSync } from "../../common/kube-helpers";
|
||||
import { loadConfigFromString } from "../../common/kube-helpers";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import type { ClusterModel } from "../../common/cluster-types";
|
||||
@ -19,6 +18,8 @@ import directoryForKubeConfigsInjectable
|
||||
from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import getCustomKubeConfigDirectoryInjectable
|
||||
from "../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||
import readFileSyncInjectable from "../../common/fs/read-file-sync.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface Pre360ClusterModel extends ClusterModel {
|
||||
kubeConfig?: string;
|
||||
@ -32,6 +33,8 @@ export default {
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
|
||||
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigDirectoryInjectable);
|
||||
const readFileSync = di.inject(readFileSyncInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
const storedClusters: Pre360ClusterModel[] = store.get("clusters") ?? [];
|
||||
const migratedClusters: ClusterModel[] = [];
|
||||
@ -55,7 +58,7 @@ export default {
|
||||
fse.writeFileSync(absPath, clusterModel.kubeConfig, { encoding: "utf-8", mode: 0o600 });
|
||||
|
||||
clusterModel.kubeConfigPath = absPath;
|
||||
clusterModel.contextName = loadConfigFromFileSync(clusterModel.kubeConfigPath).config.getCurrentContext();
|
||||
clusterModel.contextName = loadConfigFromString(readFileSync(absPath)).config.getCurrentContext();
|
||||
delete clusterModel.kubeConfig;
|
||||
|
||||
} catch (error) {
|
||||
@ -71,7 +74,7 @@ export default {
|
||||
if (clusterModel.preferences?.icon) {
|
||||
migrationLog(`migrating ${clusterModel.preferences.icon} for ${clusterModel.preferences.clusterName}`);
|
||||
const iconPath = clusterModel.preferences.icon.replace("store://", "");
|
||||
const fileData = fse.readFileSync(path.join(userDataPath, iconPath));
|
||||
const fileData = fse.readFileSync(joinPaths(userDataPath, iconPath));
|
||||
|
||||
clusterModel.preferences.icon = `data:;base64,${fileData.toString("base64")}`;
|
||||
} else {
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fse from "fs-extra";
|
||||
import type { ClusterModel } from "../../common/cluster-types";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isErrnoException } from "../../common/utils";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface Pre500WorkspaceStoreModel {
|
||||
workspaces: {
|
||||
@ -24,9 +24,10 @@ export default {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
try {
|
||||
const workspaceData: Pre500WorkspaceStoreModel = fse.readJsonSync(path.join(userDataPath, "lens-workspace-store.json"));
|
||||
const workspaceData: Pre500WorkspaceStoreModel = fse.readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
|
||||
const workspaces = new Map<string, string>(); // mapping from WorkspaceId to name
|
||||
|
||||
for (const { id, name } of workspaceData.workspaces) {
|
||||
|
||||
@ -7,12 +7,11 @@ import type { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } f
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import { generateNewIdFor } from "../utils";
|
||||
import path from "path";
|
||||
import { moveSync, removeSync } from "fs-extra";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isDefined } from "../../common/utils";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
function mergePrometheusPreferences(left: ClusterPrometheusPreferences, right: ClusterPrometheusPreferences): ClusterPrometheusPreferences {
|
||||
if (left.prometheus && left.prometheusProvider) {
|
||||
@ -79,29 +78,29 @@ function mergeClusterModel(prev: ClusterModel, right: Omit<ClusterModel, "id">):
|
||||
};
|
||||
}
|
||||
|
||||
function moveStorageFolder({ folder, newId, oldId }: { folder: string; newId: string; oldId: string }): void {
|
||||
const oldPath = path.resolve(folder, `${oldId}.json`);
|
||||
const newPath = path.resolve(folder, `${newId}.json`);
|
||||
|
||||
try {
|
||||
moveSync(oldPath, newPath);
|
||||
} catch (error) {
|
||||
if (String(error).includes("dest already exists")) {
|
||||
migrationLog(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`);
|
||||
removeSync(oldPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
version: "5.0.0-beta.13",
|
||||
run(store) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
const folder = path.resolve(userDataPath, "lens-local-storage");
|
||||
const moveStorageFolder = ({ folder, newId, oldId }: { folder: string; newId: string; oldId: string }): void => {
|
||||
const oldPath = joinPaths(folder, `${oldId}.json`);
|
||||
const newPath = joinPaths(folder, `${newId}.json`);
|
||||
|
||||
try {
|
||||
moveSync(oldPath, newPath);
|
||||
} catch (error) {
|
||||
if (String(error).includes("dest already exists")) {
|
||||
migrationLog(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`);
|
||||
removeSync(oldPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const folder = joinPaths(userDataPath, "lens-local-storage");
|
||||
const oldClusters: ClusterModel[] = store.get("clusters") ?? [];
|
||||
const clusters = new Map<string, ClusterModel>();
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
|
||||
import fse from "fs-extra";
|
||||
import { isNull } from "lodash";
|
||||
import path from "path";
|
||||
import * as uuid from "uuid";
|
||||
import type { ClusterStoreModel } from "../../common/cluster-store/cluster-store";
|
||||
import type { Hotbar, HotbarItem } from "../../common/hotbars/types";
|
||||
@ -17,6 +16,7 @@ import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-glo
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import catalogCatalogEntityInjectable from "../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
|
||||
import { isDefined, isErrnoException } from "../../common/utils";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface Pre500WorkspaceStoreModel {
|
||||
workspaces: {
|
||||
@ -40,6 +40,7 @@ export default {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
// Hotbars might be empty, if some of the previous migrations weren't run
|
||||
if (hotbars.length === 0) {
|
||||
@ -56,8 +57,8 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
const workspaceStoreData: Pre500WorkspaceStoreModel = fse.readJsonSync(path.join(userDataPath, "lens-workspace-store.json"));
|
||||
const { clusters = [] }: ClusterStoreModel = fse.readJSONSync(path.join(userDataPath, "lens-cluster-store.json"));
|
||||
const workspaceStoreData: Pre500WorkspaceStoreModel = fse.readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
|
||||
const { clusters = [] }: ClusterStoreModel = fse.readJSONSync(joinPaths(userDataPath, "lens-cluster-store.json"));
|
||||
const workspaceHotbars = new Map<string, PartialHotbar>(); // mapping from WorkspaceId to HotBar
|
||||
|
||||
for (const { id, name } of workspaceStoreData.workspaces) {
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import type { ClusterStoreModel } from "../../common/cluster-store/cluster-store";
|
||||
import type { KubeconfigSyncEntry, UserPreferencesModel } from "../../common/user-store";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import { isErrnoException, isLogicalChildPath } from "../../common/utils";
|
||||
import { isErrnoException } from "../../common/utils";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable
|
||||
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForKubeConfigsInjectable
|
||||
from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForKubeConfigsInjectable from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import isLogicalChildPathInjectable from "../../common/path/is-logical-child-path.injectable";
|
||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||
|
||||
export default {
|
||||
version: "5.0.3-beta.1",
|
||||
@ -27,18 +27,21 @@ export default {
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const isLogicalChildPath = di.inject(isLogicalChildPathInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
const { clusters = [] }: ClusterStoreModel = JSON.parse(readFileSync(path.resolve(userDataPath, "lens-cluster-store.json"), "utf-8")) ?? {};
|
||||
const extensionDataDir = path.resolve(userDataPath, "extension_data");
|
||||
const { clusters = [] }: ClusterStoreModel = JSON.parse(readFileSync(joinPaths(userDataPath, "lens-cluster-store.json"), "utf-8")) ?? {};
|
||||
const extensionDataDir = joinPaths(userDataPath, "extension_data");
|
||||
const syncPaths = new Set(syncKubeconfigEntries.map(s => s.filePath));
|
||||
|
||||
syncPaths.add(path.join(os.homedir(), ".kube"));
|
||||
syncPaths.add(joinPaths(os.homedir(), ".kube"));
|
||||
|
||||
for (const cluster of clusters) {
|
||||
if (!cluster.kubeConfigPath) {
|
||||
continue;
|
||||
}
|
||||
const dirOfKubeconfig = path.dirname(cluster.kubeConfigPath);
|
||||
const dirOfKubeconfig = getDirnameOfPath(cluster.kubeConfigPath);
|
||||
|
||||
if (dirOfKubeconfig === kubeConfigsPath) {
|
||||
migrationLog(`Skipping ${cluster.id} because kubeConfigPath is under the stored KubeConfig folder`);
|
||||
|
||||
@ -10,7 +10,6 @@ import fse from "fs-extra";
|
||||
import { debounce } from "lodash";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import path from "path";
|
||||
import React from "react";
|
||||
import * as uuid from "uuid";
|
||||
import { appEventBus } from "../../../common/app-event-bus/event-bus";
|
||||
@ -25,6 +24,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||
import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable";
|
||||
import type { GetDirnameOfPath } from "../../../common/path/get-dirname.injectable";
|
||||
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
|
||||
|
||||
interface Option {
|
||||
config: KubeConfig;
|
||||
@ -34,6 +35,7 @@ interface Option {
|
||||
interface Dependencies {
|
||||
getCustomKubeConfigDirectory: (directoryName: string) => string;
|
||||
navigateToCatalog: NavigateToCatalog;
|
||||
getDirnameOfPath: GetDirnameOfPath;
|
||||
}
|
||||
|
||||
function getContexts(config: KubeConfig): Map<string, Option> {
|
||||
@ -90,7 +92,7 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
||||
try {
|
||||
const absPath = this.props.getCustomKubeConfigDirectory(uuid.v4());
|
||||
|
||||
await fse.ensureDir(path.dirname(absPath));
|
||||
await fse.ensureDir(this.props.getDirnameOfPath(absPath));
|
||||
await fse.writeFile(absPath, this.customConfig.trim(), { encoding: "utf-8", mode: 0o600 });
|
||||
|
||||
Notifications.ok(`Successfully added ${this.kubeContexts.size} new cluster(s)`);
|
||||
@ -155,10 +157,8 @@ class NonInjectedAddCluster extends React.Component<Dependencies> {
|
||||
|
||||
export const AddCluster = withInjectables<Dependencies>(NonInjectedAddCluster, {
|
||||
getProps: (di) => ({
|
||||
getCustomKubeConfigDirectory: di.inject(
|
||||
getCustomKubeConfigDirectoryInjectable,
|
||||
),
|
||||
|
||||
getCustomKubeConfigDirectory: di.inject(getCustomKubeConfigDirectoryInjectable),
|
||||
navigateToCatalog: di.inject(navigateToCatalogInjectable),
|
||||
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -18,8 +18,8 @@ import extensionDiscoveryInjectable from "../../../../extensions/extension-disco
|
||||
import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import directoryForDownloadsInjectable from "../../../../common/app-paths/directory-for-downloads/directory-for-downloads.injectable";
|
||||
import assert from "assert";
|
||||
import type { InstallFromInput } from "../install-from-input/install-from-input";
|
||||
import installFromInputInjectable from "../install-from-input/install-from-input.injectable";
|
||||
import type { InstallExtensionFromInput } from "../install-extension-from-input.injectable";
|
||||
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import { observable, when } from "mobx";
|
||||
@ -46,7 +46,7 @@ jest.mock("../../../../common/utils/tar");
|
||||
describe("Extensions", () => {
|
||||
let extensionLoader: ExtensionLoader;
|
||||
let extensionDiscovery: ExtensionDiscovery;
|
||||
let installFromInput: jest.MockedFunction<InstallFromInput>;
|
||||
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
|
||||
let extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
let render: DiRender;
|
||||
let deleteFileMock: jest.MockedFunction<DeleteFile>;
|
||||
@ -59,8 +59,8 @@ describe("Extensions", () => {
|
||||
|
||||
render = renderFor(di);
|
||||
|
||||
installFromInput = jest.fn();
|
||||
di.override(installFromInputInjectable, () => installFromInput);
|
||||
installExtensionFromInput = jest.fn();
|
||||
di.override(installExtensionFromInputInjectable, () => installExtensionFromInput);
|
||||
|
||||
deleteFileMock = jest.fn();
|
||||
di.override(deleteFileInjectable, () => deleteFileMock);
|
||||
@ -126,7 +126,7 @@ describe("Extensions", () => {
|
||||
const resolveInstall = observable.box(false);
|
||||
|
||||
deleteFileMock.mockReturnValue(Promise.resolve());
|
||||
installFromInput.mockImplementation(async (input) => {
|
||||
installExtensionFromInput.mockImplementation(async (input) => {
|
||||
expect(input).toBe("https://test.extensionurl/package.tgz");
|
||||
|
||||
const clear = extensionInstallationStateStore.startPreInstall();
|
||||
|
||||
@ -2,22 +2,18 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ExtendableDisposer } from "../../../common/utils";
|
||||
import { downloadFile, downloadJson } from "../../../common/utils";
|
||||
import { Notifications } from "../notifications";
|
||||
import React from "react";
|
||||
import path from "path";
|
||||
import { SemVer } from "semver";
|
||||
import URLParse from "url-parse";
|
||||
import type { InstallRequest } from "./attempt-install/install-request";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { Confirm } from "../confirm-dialog/confirm.injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import confirmInjectable from "../confirm-dialog/confirm.injectable";
|
||||
import { reduce } from "lodash";
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
|
||||
export interface ExtensionInfo {
|
||||
name: string;
|
||||
@ -27,126 +23,113 @@ export interface ExtensionInfo {
|
||||
|
||||
export type AttemptInstallByInfo = (info: ExtensionInfo) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
attemptInstall: (request: InstallRequest, d: ExtendableDisposer) => Promise<void>;
|
||||
getBaseRegistryUrl: () => Promise<string>;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
confirm: Confirm;
|
||||
}
|
||||
const attemptInstallByInfoInjectable = getInjectable({
|
||||
id: "attempt-install-by-info",
|
||||
instantiate: (di): AttemptInstallByInfo => {
|
||||
const attemptInstall = di.inject(attemptInstallInjectable);
|
||||
const getBaseRegistryUrl = di.inject(getBaseRegistryUrlInjectable);
|
||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||
const confirm = di.inject(confirmInjectable);
|
||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||
|
||||
const attemptInstallByInfo = ({
|
||||
attemptInstall,
|
||||
getBaseRegistryUrl,
|
||||
extensionInstallationStateStore,
|
||||
confirm,
|
||||
}: Dependencies): AttemptInstallByInfo => (
|
||||
async (info) => {
|
||||
const { name, version, requireConfirmation = false } = info;
|
||||
const disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const baseUrl = await getBaseRegistryUrl();
|
||||
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
||||
let json: any;
|
||||
let finalVersion = version;
|
||||
return async (info) => {
|
||||
const { name, version, requireConfirmation = false } = info;
|
||||
const disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const baseUrl = await getBaseRegistryUrl();
|
||||
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
|
||||
let json: any;
|
||||
let finalVersion = version;
|
||||
|
||||
try {
|
||||
json = await downloadJson({ url: registryUrl }).promise;
|
||||
try {
|
||||
json = await downloadJson({ url: registryUrl }).promise;
|
||||
|
||||
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
|
||||
const message = json?.error ? `: ${json.error}` : "";
|
||||
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
|
||||
const message = json?.error ? `: ${json.error}` : "";
|
||||
|
||||
Notifications.error(`Failed to get registry information for that extension${message}`);
|
||||
|
||||
return disposer();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
// assume invalid JSON
|
||||
console.warn("Set registry has invalid json", { url: baseUrl }, error);
|
||||
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
|
||||
} else {
|
||||
console.error("Failed to download registry information", error);
|
||||
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
|
||||
}
|
||||
|
||||
return disposer();
|
||||
}
|
||||
|
||||
if (version) {
|
||||
if (!json.versions[version]) {
|
||||
if (json["dist-tags"][version]) {
|
||||
finalVersion = json["dist-tags"][version];
|
||||
} else {
|
||||
Notifications.error((
|
||||
<p>
|
||||
{"The "}
|
||||
<em>{name}</em>
|
||||
{" extension does not have a version or tag "}
|
||||
<code>{version}</code>
|
||||
.
|
||||
</p>
|
||||
));
|
||||
Notifications.error(`Failed to get registry information for that extension${message}`);
|
||||
|
||||
return disposer();
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
// assume invalid JSON
|
||||
console.warn("Set registry has invalid json", { url: baseUrl }, error);
|
||||
Notifications.error("Failed to get valid registry information for that extension. Registry did not return valid JSON");
|
||||
} else {
|
||||
console.error("Failed to download registry information", error);
|
||||
Notifications.error(`Failed to get valid registry information for that extension. ${error}`);
|
||||
}
|
||||
|
||||
return disposer();
|
||||
}
|
||||
} else {
|
||||
const versions = Object.keys(json.versions)
|
||||
.map(version => new SemVer(version, { loose: true }))
|
||||
|
||||
if (version) {
|
||||
if (!json.versions[version]) {
|
||||
if (json["dist-tags"][version]) {
|
||||
finalVersion = json["dist-tags"][version];
|
||||
} else {
|
||||
Notifications.error((
|
||||
<p>
|
||||
{"The "}
|
||||
<em>{name}</em>
|
||||
{" extension does not have a version or tag "}
|
||||
<code>{version}</code>
|
||||
.
|
||||
</p>
|
||||
));
|
||||
|
||||
return disposer();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const versions = Object.keys(json.versions)
|
||||
.map(version => new SemVer(version, { loose: true }))
|
||||
// ignore pre-releases for auto picking the version
|
||||
.filter(version => version.prerelease.length === 0);
|
||||
.filter(version => version.prerelease.length === 0);
|
||||
|
||||
const latestVersion = reduce(versions, (prev, curr) => prev.compareMain(curr) === -1 ? curr : prev);
|
||||
const latestVersion = reduce(versions, (prev, curr) => prev.compareMain(curr) === -1 ? curr : prev);
|
||||
|
||||
if (!latestVersion) {
|
||||
console.error("No versions supplied for that extension", { name });
|
||||
Notifications.error(`No versions found for ${name}`);
|
||||
if (!latestVersion) {
|
||||
console.error("No versions supplied for that extension", { name });
|
||||
Notifications.error(`No versions found for ${name}`);
|
||||
|
||||
return disposer();
|
||||
return disposer();
|
||||
}
|
||||
|
||||
finalVersion = latestVersion.format();
|
||||
}
|
||||
|
||||
finalVersion = latestVersion.format();
|
||||
}
|
||||
if (requireConfirmation) {
|
||||
const proceed = await confirm({
|
||||
message: (
|
||||
<p>
|
||||
Are you sure you want to install
|
||||
{" "}
|
||||
<b>
|
||||
{name}
|
||||
@
|
||||
{finalVersion}
|
||||
</b>
|
||||
?
|
||||
</p>
|
||||
),
|
||||
labelCancel: "Cancel",
|
||||
labelOk: "Install",
|
||||
});
|
||||
|
||||
if (requireConfirmation) {
|
||||
const proceed = await confirm({
|
||||
message: (
|
||||
<p>
|
||||
Are you sure you want to install
|
||||
{" "}
|
||||
<b>
|
||||
{name}
|
||||
@
|
||||
{finalVersion}
|
||||
</b>
|
||||
?
|
||||
</p>
|
||||
),
|
||||
labelCancel: "Cancel",
|
||||
labelOk: "Install",
|
||||
});
|
||||
|
||||
if (!proceed) {
|
||||
return disposer();
|
||||
if (!proceed) {
|
||||
return disposer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const url = json.versions[finalVersion!].dist.tarball;
|
||||
const fileName = path.basename(url);
|
||||
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const url = json.versions[finalVersion!].dist.tarball;
|
||||
const fileName = getBasenameOfPath(url);
|
||||
const { promise: dataP } = downloadFile({ url, timeout: 10 * 60 * 1000 });
|
||||
|
||||
return attemptInstall({ fileName, dataP }, disposer);
|
||||
}
|
||||
);
|
||||
|
||||
const attemptInstallByInfoInjectable = getInjectable({
|
||||
id: "attempt-install-by-info",
|
||||
instantiate: (di) => attemptInstallByInfo({
|
||||
attemptInstall: di.inject(attemptInstallInjectable),
|
||||
getBaseRegistryUrl: di.inject(getBaseRegistryUrlInjectable),
|
||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||
confirm: di.inject(confirmInjectable),
|
||||
}),
|
||||
return attemptInstall({ fileName, dataP }, disposer);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default attemptInstallByInfoInjectable;
|
||||
|
||||
@ -2,12 +2,8 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type {
|
||||
Disposer,
|
||||
ExtendableDisposer } from "../../../../common/utils";
|
||||
import {
|
||||
disposer,
|
||||
} from "../../../../common/utils";
|
||||
import type { ExtendableDisposer } from "../../../../common/utils";
|
||||
import { disposer } from "../../../../common/utils";
|
||||
import { Notifications } from "../../notifications";
|
||||
import { Button } from "../../button";
|
||||
import type { ExtensionLoader } from "../../../../extensions/extension-loader";
|
||||
@ -15,29 +11,19 @@ import type { LensExtensionId } from "../../../../extensions/lens-extension";
|
||||
import React from "react";
|
||||
import { remove as removeDir } from "fs-extra";
|
||||
import { shell } from "electron";
|
||||
import type { InstallRequestValidated } from "./create-temp-files-and-validate/create-temp-files-and-validate";
|
||||
import type { InstallRequest } from "./install-request";
|
||||
import type {
|
||||
ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import {
|
||||
ExtensionInstallationState,
|
||||
} from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import { ExtensionInstallationState } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { UnpackExtension } from "./unpack-extension/unpack-extension.injectable";
|
||||
import type { CreateTempFilesAndValidate } from "./create-temp-files-and-validate/create-temp-files-and-validate.injectable";
|
||||
import type { GetExtensionDestFolder } from "./get-extension-dest-folder/get-extension-dest-folder.injectable";
|
||||
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
uninstallExtension: (id: LensExtensionId) => Promise<boolean>;
|
||||
|
||||
unpackExtension: (
|
||||
request: InstallRequestValidated,
|
||||
disposeDownloading: Disposer,
|
||||
) => Promise<void>;
|
||||
|
||||
createTempFilesAndValidate: (
|
||||
installRequest: InstallRequest,
|
||||
) => Promise<InstallRequestValidated | null>;
|
||||
|
||||
getExtensionDestFolder: (name: string) => string;
|
||||
|
||||
unpackExtension: UnpackExtension;
|
||||
createTempFilesAndValidate: CreateTempFilesAndValidate;
|
||||
getExtensionDestFolder: GetExtensionDestFolder;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
@ -51,6 +37,8 @@ export const attemptInstall =
|
||||
extensionInstallationStateStore,
|
||||
}: Dependencies) =>
|
||||
async (request: InstallRequest, d?: ExtendableDisposer): Promise<void> => {
|
||||
console.log("Attempting to install extension");
|
||||
|
||||
const dispose = disposer(
|
||||
extensionInstallationStateStore.startPreInstall(),
|
||||
d,
|
||||
|
||||
@ -3,16 +3,102 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { createTempFilesAndValidate } from "./create-temp-files-and-validate";
|
||||
import extensionDiscoveryInjectable from "../../../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||
import React from "react";
|
||||
import type { LensExtensionId, LensExtensionManifest } from "../../../../../extensions/lens-extension";
|
||||
import { getMessageFromError } from "../../get-message-from-error/get-message-from-error";
|
||||
import type { InstallRequest } from "../install-request";
|
||||
import { validatePackage } from "../validate-package/validate-package";
|
||||
import joinPathsInjectable from "../../../../../common/path/join-paths.injectable";
|
||||
import tempDirectoryPathInjectable from "../../../../../common/os/temp-directory-path.injectable";
|
||||
import ensureDirInjectable from "../../../../../common/fs/ensure-dir.injectable";
|
||||
import writeFileInjectable from "../../../../../common/fs/write-file.injectable";
|
||||
import loggerInjectable from "../../../../../common/logger.injectable";
|
||||
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
|
||||
|
||||
export interface InstallRequestValidated {
|
||||
fileName: string;
|
||||
data: Buffer;
|
||||
id: LensExtensionId;
|
||||
manifest: LensExtensionManifest;
|
||||
tempFile: string; // temp system path to packed extension for unpacking
|
||||
}
|
||||
|
||||
export type CreateTempFilesAndValidate = (req: InstallRequest) => Promise<InstallRequestValidated | null>;
|
||||
|
||||
const createTempFilesAndValidateInjectable = getInjectable({
|
||||
id: "create-temp-files-and-validate",
|
||||
|
||||
instantiate: (di) =>
|
||||
createTempFilesAndValidate({
|
||||
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
||||
}),
|
||||
instantiate: (di) => {
|
||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const tempDirectoryPath = di.inject(tempDirectoryPathInjectable);
|
||||
const ensureDir = di.inject(ensureDirInjectable);
|
||||
const writeFile = di.inject(writeFileInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
|
||||
const baseTempExtensionsDirectory = joinPaths(tempDirectoryPath, "lens-extensions");
|
||||
const getExtensionPackageTemp = (fileName: string) => joinPaths(baseTempExtensionsDirectory, fileName);
|
||||
|
||||
return async ({
|
||||
fileName,
|
||||
dataP,
|
||||
}: InstallRequest): Promise<InstallRequestValidated | null> => {
|
||||
// copy files to temp
|
||||
await ensureDir(baseTempExtensionsDirectory);
|
||||
|
||||
// validate packages
|
||||
const tempFile = getExtensionPackageTemp(fileName);
|
||||
|
||||
try {
|
||||
const data = await dataP;
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await writeFile(tempFile, data);
|
||||
logger.info("validating package", tempFile);
|
||||
const manifest = await validatePackage(tempFile);
|
||||
const id = joinPaths(
|
||||
extensionDiscovery.nodeModulesPath,
|
||||
manifest.name,
|
||||
"package.json",
|
||||
);
|
||||
|
||||
return {
|
||||
fileName,
|
||||
data,
|
||||
manifest,
|
||||
tempFile,
|
||||
id,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-INSTALLATION]: installing ${fileName} has failed: ${message}`,
|
||||
{ error },
|
||||
);
|
||||
showErrorNotification(
|
||||
<div className="flex column gaps">
|
||||
<p>
|
||||
{"Installing "}
|
||||
<em>{fileName}</em>
|
||||
{" has failed, skipping."}
|
||||
</p>
|
||||
<p>
|
||||
{"Reason: "}
|
||||
<em>{message}</em>
|
||||
</p>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createTempFilesAndValidateInjectable;
|
||||
|
||||
@ -1,94 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { validatePackage } from "../validate-package/validate-package";
|
||||
import type { ExtensionDiscovery } from "../../../../../extensions/extension-discovery/extension-discovery";
|
||||
import { getMessageFromError } from "../../get-message-from-error/get-message-from-error";
|
||||
import logger from "../../../../../main/logger";
|
||||
import { Notifications } from "../../../notifications";
|
||||
import path from "path";
|
||||
import fse from "fs-extra";
|
||||
import React from "react";
|
||||
import os from "os";
|
||||
import type {
|
||||
LensExtensionId,
|
||||
LensExtensionManifest,
|
||||
} from "../../../../../extensions/lens-extension";
|
||||
import type { InstallRequest } from "../install-request";
|
||||
|
||||
export interface InstallRequestValidated {
|
||||
fileName: string;
|
||||
data: Buffer;
|
||||
id: LensExtensionId;
|
||||
manifest: LensExtensionManifest;
|
||||
tempFile: string; // temp system path to packed extension for unpacking
|
||||
}
|
||||
|
||||
interface Dependencies {
|
||||
extensionDiscovery: ExtensionDiscovery;
|
||||
}
|
||||
|
||||
export const createTempFilesAndValidate =
|
||||
({ extensionDiscovery }: Dependencies) =>
|
||||
async ({
|
||||
fileName,
|
||||
dataP,
|
||||
}: InstallRequest): Promise<InstallRequestValidated | null> => {
|
||||
// copy files to temp
|
||||
await fse.ensureDir(getExtensionPackageTemp());
|
||||
|
||||
// validate packages
|
||||
const tempFile = getExtensionPackageTemp(fileName);
|
||||
|
||||
try {
|
||||
const data = await dataP;
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await fse.writeFile(tempFile, data);
|
||||
const manifest = await validatePackage(tempFile);
|
||||
const id = path.join(
|
||||
extensionDiscovery.nodeModulesPath,
|
||||
manifest.name,
|
||||
"package.json",
|
||||
);
|
||||
|
||||
return {
|
||||
fileName,
|
||||
data,
|
||||
manifest,
|
||||
tempFile,
|
||||
id,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-INSTALLATION]: installing ${fileName} has failed: ${message}`,
|
||||
{ error },
|
||||
);
|
||||
Notifications.error(
|
||||
<div className="flex column gaps">
|
||||
<p>
|
||||
{"Installing "}
|
||||
<em>{fileName}</em>
|
||||
{" has failed, skipping."}
|
||||
</p>
|
||||
<p>
|
||||
{"Reason: "}
|
||||
<em>{message}</em>
|
||||
</p>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
function getExtensionPackageTemp(fileName = "") {
|
||||
return path.join(os.tmpdir(), "lens-extensions", fileName);
|
||||
}
|
||||
@ -3,18 +3,21 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
import joinPathsInjectable from "../../../../../common/path/join-paths.injectable";
|
||||
import extensionDiscoveryInjectable from "../../../../../extensions/extension-discovery/extension-discovery.injectable";
|
||||
import { sanitizeExtensionName } from "../../../../../extensions/lens-extension";
|
||||
|
||||
import { getExtensionDestFolder } from "./get-extension-dest-folder";
|
||||
export type GetExtensionDestFolder = (extensionName: string) => string;
|
||||
|
||||
const getExtensionDestFolderInjectable = getInjectable({
|
||||
id: "get-extension-dest-folder",
|
||||
|
||||
instantiate: (di) =>
|
||||
getExtensionDestFolder({
|
||||
extensionDiscovery: di.inject(extensionDiscoveryInjectable),
|
||||
}),
|
||||
instantiate: (di): GetExtensionDestFolder => {
|
||||
const extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (name) => joinPaths(extensionDiscovery.localFolderPath, sanitizeExtensionName(name));
|
||||
},
|
||||
});
|
||||
|
||||
export default getExtensionDestFolderInjectable;
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ExtensionDiscovery } from "../../../../../extensions/extension-discovery/extension-discovery";
|
||||
import { sanitizeExtensionName } from "../../../../../extensions/lens-extension";
|
||||
import path from "path";
|
||||
|
||||
interface Dependencies {
|
||||
extensionDiscovery: ExtensionDiscovery;
|
||||
}
|
||||
|
||||
export const getExtensionDestFolder =
|
||||
({ extensionDiscovery }: Dependencies) =>
|
||||
(name: string) =>
|
||||
path.join(extensionDiscovery.localFolderPath, sanitizeExtensionName(name));
|
||||
@ -2,23 +2,129 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { unpackExtension } from "./unpack-extension";
|
||||
import extensionLoaderInjectable from "../../../../../extensions/extension-loader/extension-loader.injectable";
|
||||
import getExtensionDestFolderInjectable
|
||||
from "../get-extension-dest-folder/get-extension-dest-folder.injectable";
|
||||
import extensionInstallationStateStoreInjectable
|
||||
from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import getExtensionDestFolderInjectable from "../get-extension-dest-folder/get-extension-dest-folder.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import type { InstallRequestValidated } from "../create-temp-files-and-validate/create-temp-files-and-validate.injectable";
|
||||
import type { Disposer } from "../../../../utils";
|
||||
import { noop } from "../../../../utils";
|
||||
import { extensionDisplayName } from "../../../../../extensions/lens-extension";
|
||||
import joinPathsInjectable from "../../../../../common/path/join-paths.injectable";
|
||||
import loggerInjectable from "../../../../../common/logger.injectable";
|
||||
import { when } from "mobx";
|
||||
import { getMessageFromError } from "../../get-message-from-error/get-message-from-error";
|
||||
import showSuccessNotificationInjectable from "../../../notifications/show-success-notification.injectable";
|
||||
import showErrorNotificationInjectable from "../../../notifications/show-error-notification.injectable";
|
||||
import getDirnameOfPathInjectable from "../../../../../common/path/get-dirname.injectable";
|
||||
import getBasenameOfPathInjectable from "../../../../../common/path/get-basename.injectable";
|
||||
import extractTarInjectable from "../../../../../common/fs/extract-tar.injectable";
|
||||
import ensureDirInjectable from "../../../../../common/fs/ensure-dir.injectable";
|
||||
import removePathInjectable from "../../../../../common/fs/remove-path.injectable";
|
||||
import deleteFileInjectable from "../../../../../common/fs/delete-file.injectable";
|
||||
import readDirectoryInjectable from "../../../../../common/fs/read-directory.injectable";
|
||||
import moveInjectable from "../../../../../common/fs/move.injectable";
|
||||
|
||||
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
|
||||
|
||||
const unpackExtensionInjectable = getInjectable({
|
||||
id: "unpack-extension",
|
||||
|
||||
instantiate: (di) =>
|
||||
unpackExtension({
|
||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||
getExtensionDestFolder: di.inject(getExtensionDestFolderInjectable),
|
||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||
}),
|
||||
instantiate: (di): UnpackExtension => {
|
||||
const extensionLoader = di.inject(extensionLoaderInjectable);
|
||||
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
|
||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const showOkNotification = di.inject(showSuccessNotificationInjectable);
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
const extractTar = di.inject(extractTarInjectable);
|
||||
const ensureDir = di.inject(ensureDirInjectable);
|
||||
const removePath = di.inject(removePathInjectable);
|
||||
const deleteFile = di.inject(deleteFileInjectable);
|
||||
const readDirectory = di.inject(readDirectoryInjectable);
|
||||
const move = di.inject(moveInjectable);
|
||||
|
||||
return async (request, disposeDownloading) => {
|
||||
const {
|
||||
id,
|
||||
fileName,
|
||||
tempFile,
|
||||
manifest: { name, version },
|
||||
} = request;
|
||||
|
||||
extensionInstallationStateStore.setInstalling(id);
|
||||
disposeDownloading?.();
|
||||
|
||||
const displayName = extensionDisplayName(name, version);
|
||||
const extensionFolder = getExtensionDestFolder(name);
|
||||
const unpackingTempFolder = joinPaths(
|
||||
getDirnameOfPath(tempFile),
|
||||
`${getBasenameOfPath(tempFile)}-unpacked`,
|
||||
);
|
||||
|
||||
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
|
||||
|
||||
try {
|
||||
// extract to temp folder first
|
||||
await removePath(unpackingTempFolder).catch(noop);
|
||||
await ensureDir(unpackingTempFolder);
|
||||
await extractTar(tempFile, { cwd: unpackingTempFolder });
|
||||
|
||||
// move contents to extensions folder
|
||||
const unpackedFiles = await readDirectory(unpackingTempFolder);
|
||||
let unpackedRootFolder = unpackingTempFolder;
|
||||
|
||||
if (unpackedFiles.length === 1) {
|
||||
// check if %extension.tgz was packed with single top folder,
|
||||
// e.g. "npm pack %ext_name" downloads file with "package" root folder within tarball
|
||||
unpackedRootFolder = joinPaths(unpackingTempFolder, unpackedFiles[0]);
|
||||
}
|
||||
|
||||
await ensureDir(extensionFolder);
|
||||
await move(unpackedRootFolder, extensionFolder, { overwrite: true });
|
||||
|
||||
// wait for the loader has actually install it
|
||||
await when(() => extensionLoader.userExtensions.has(id));
|
||||
|
||||
// Enable installed extensions by default.
|
||||
extensionLoader.setIsEnabled(id, true);
|
||||
|
||||
showOkNotification(
|
||||
<p>
|
||||
{"Extension "}
|
||||
<b>{displayName}</b>
|
||||
{" successfully installed!"}
|
||||
</p>,
|
||||
);
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-INSTALLATION]: installing ${request.fileName} has failed: ${message}`,
|
||||
{ error },
|
||||
);
|
||||
showErrorNotification(
|
||||
<p>
|
||||
{"Installing extension "}
|
||||
<b>{displayName}</b>
|
||||
{" has failed: "}
|
||||
<em>{message}</em>
|
||||
</p>,
|
||||
);
|
||||
} finally {
|
||||
// Remove install state once finished
|
||||
extensionInstallationStateStore.clearInstalling(id);
|
||||
|
||||
// clean up
|
||||
removePath(unpackingTempFolder).catch(noop);
|
||||
deleteFile(tempFile).catch(noop);
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default unpackExtensionInjectable;
|
||||
|
||||
@ -1,106 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { InstallRequestValidated } from "../create-temp-files-and-validate/create-temp-files-and-validate";
|
||||
import type { Disposer } from "../../../../../common/utils";
|
||||
import { extractTar, noop } from "../../../../../common/utils";
|
||||
import { extensionDisplayName } from "../../../../../extensions/lens-extension";
|
||||
import logger from "../../../../../main/logger";
|
||||
import type { ExtensionLoader } from "../../../../../extensions/extension-loader";
|
||||
import { Notifications } from "../../../notifications";
|
||||
import { getMessageFromError } from "../../get-message-from-error/get-message-from-error";
|
||||
import path from "path";
|
||||
import fse from "fs-extra";
|
||||
import { when } from "mobx";
|
||||
import React from "react";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
getExtensionDestFolder: (name: string) => string;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
export const unpackExtension =
|
||||
({
|
||||
extensionLoader,
|
||||
getExtensionDestFolder,
|
||||
extensionInstallationStateStore,
|
||||
}: Dependencies) =>
|
||||
async (request: InstallRequestValidated, disposeDownloading?: Disposer) => {
|
||||
const {
|
||||
id,
|
||||
fileName,
|
||||
tempFile,
|
||||
manifest: { name, version },
|
||||
} = request;
|
||||
|
||||
extensionInstallationStateStore.setInstalling(id);
|
||||
disposeDownloading?.();
|
||||
|
||||
const displayName = extensionDisplayName(name, version);
|
||||
const extensionFolder = getExtensionDestFolder(name);
|
||||
const unpackingTempFolder = path.join(
|
||||
path.dirname(tempFile),
|
||||
`${path.basename(tempFile)}-unpacked`,
|
||||
);
|
||||
|
||||
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
|
||||
|
||||
try {
|
||||
// extract to temp folder first
|
||||
await fse.remove(unpackingTempFolder).catch(noop);
|
||||
await fse.ensureDir(unpackingTempFolder);
|
||||
await extractTar(tempFile, { cwd: unpackingTempFolder });
|
||||
|
||||
// move contents to extensions folder
|
||||
const unpackedFiles = await fse.readdir(unpackingTempFolder);
|
||||
let unpackedRootFolder = unpackingTempFolder;
|
||||
|
||||
if (unpackedFiles.length === 1) {
|
||||
// check if %extension.tgz was packed with single top folder,
|
||||
// e.g. "npm pack %ext_name" downloads file with "package" root folder within tarball
|
||||
unpackedRootFolder = path.join(unpackingTempFolder, unpackedFiles[0]);
|
||||
}
|
||||
|
||||
await fse.ensureDir(extensionFolder);
|
||||
await fse.move(unpackedRootFolder, extensionFolder, { overwrite: true });
|
||||
|
||||
// wait for the loader has actually install it
|
||||
await when(() => extensionLoader.userExtensions.has(id));
|
||||
|
||||
// Enable installed extensions by default.
|
||||
extensionLoader.setIsEnabled(id, true);
|
||||
|
||||
Notifications.ok(
|
||||
<p>
|
||||
{"Extension "}
|
||||
<b>{displayName}</b>
|
||||
{" successfully installed!"}
|
||||
</p>,
|
||||
);
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(
|
||||
`[EXTENSION-INSTALLATION]: installing ${request.fileName} has failed: ${message}`,
|
||||
{ error },
|
||||
);
|
||||
Notifications.error(
|
||||
<p>
|
||||
{"Installing extension "}
|
||||
<b>{displayName}</b>
|
||||
{" has failed: "}
|
||||
<em>{message}</em>
|
||||
</p>,
|
||||
);
|
||||
} finally {
|
||||
// Remove install state once finished
|
||||
extensionInstallationStateStore.clearInstalling(id);
|
||||
|
||||
// clean up
|
||||
fse.remove(unpackingTempFolder).catch(noop);
|
||||
fse.unlink(tempFile).catch(noop);
|
||||
}
|
||||
};
|
||||
@ -3,16 +3,29 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { attemptInstalls } from "./attempt-installs";
|
||||
import getBasenameOfPathInjectable from "../../../../common/path/get-basename.injectable";
|
||||
import attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||
import readFileNotifyInjectable from "../read-file-notify/read-file-notify.injectable";
|
||||
|
||||
export type AttemptInstalls = (filePaths: string[]) => Promise<void>;
|
||||
|
||||
const attemptInstallsInjectable = getInjectable({
|
||||
id: "attempt-installs",
|
||||
|
||||
instantiate: (di) =>
|
||||
attemptInstalls({
|
||||
attemptInstall: di.inject(attemptInstallInjectable),
|
||||
}),
|
||||
instantiate: (di): AttemptInstalls => {
|
||||
const attemptInstall = di.inject(attemptInstallInjectable);
|
||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||
const readFileNotify = di.inject(readFileNotifyInjectable);
|
||||
|
||||
return async (filePaths) => {
|
||||
await Promise.allSettled(filePaths.map(filePath => (
|
||||
attemptInstall({
|
||||
fileName: getBasenameOfPath(filePath),
|
||||
dataP: readFileNotify(filePath),
|
||||
})
|
||||
)));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default attemptInstallsInjectable;
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
||||
import path from "path";
|
||||
import type { InstallRequest } from "../attempt-install/install-request";
|
||||
|
||||
interface Dependencies {
|
||||
attemptInstall: (request: InstallRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
export const attemptInstalls =
|
||||
({ attemptInstall }: Dependencies) =>
|
||||
async (filePaths: string[]): Promise<void> => {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
promises.push(
|
||||
attemptInstall({
|
||||
fileName: path.basename(filePath),
|
||||
dataP: readFileNotify(filePath),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
};
|
||||
@ -28,21 +28,21 @@ import enableExtensionInjectable from "./enable-extension/enable-extension.injec
|
||||
import disableExtensionInjectable from "./disable-extension/disable-extension.injectable";
|
||||
import type { ConfirmUninstallExtension } from "./confirm-uninstall-extension.injectable";
|
||||
import confirmUninstallExtensionInjectable from "./confirm-uninstall-extension.injectable";
|
||||
import installFromInputInjectable from "./install-from-input/install-from-input.injectable";
|
||||
import type { InstallExtensionFromInput } from "./install-extension-from-input.injectable";
|
||||
import installExtensionFromInputInjectable from "./install-extension-from-input.injectable";
|
||||
import installFromSelectFileDialogInjectable from "./install-from-select-file-dialog.injectable";
|
||||
import type { LensExtensionId } from "../../../extensions/lens-extension";
|
||||
import installOnDropInjectable from "./install-on-drop/install-on-drop.injectable";
|
||||
import { supportedExtensionFormats } from "./supported-extension-formats";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
import type { InstallFromInput } from "./install-from-input/install-from-input";
|
||||
|
||||
interface Dependencies {
|
||||
userExtensions: IComputedValue<InstalledExtension[]>;
|
||||
enableExtension: (id: LensExtensionId) => void;
|
||||
disableExtension: (id: LensExtensionId) => void;
|
||||
confirmUninstallExtension: ConfirmUninstallExtension;
|
||||
installFromInput: InstallFromInput;
|
||||
installExtensionFromInput: InstallExtensionFromInput;
|
||||
installFromSelectFileDialog: () => Promise<void>;
|
||||
installOnDrop: (files: File[]) => Promise<void>;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
@ -107,7 +107,7 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
|
||||
<Install
|
||||
supportedFormats={supportedExtensionFormats}
|
||||
onChange={value => (this.installPath = value)}
|
||||
installFromInput={() => this.props.installFromInput(this.installPath)}
|
||||
installFromInput={() => this.props.installExtensionFromInput(this.installPath)}
|
||||
installFromSelectFileDialog={this.props.installFromSelectFileDialog}
|
||||
installPath={this.installPath}
|
||||
/>
|
||||
@ -131,7 +131,7 @@ export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
|
||||
enableExtension: di.inject(enableExtensionInjectable),
|
||||
disableExtension: di.inject(disableExtensionInjectable),
|
||||
confirmUninstallExtension: di.inject(confirmUninstallExtensionInjectable),
|
||||
installFromInput: di.inject(installFromInputInjectable),
|
||||
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
|
||||
installOnDrop: di.inject(installOnDropInjectable),
|
||||
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
|
||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import React from "react";
|
||||
import type { ExtendableDisposer } from "../../../common/utils";
|
||||
import { downloadFile } from "../../../common/utils";
|
||||
import { InputValidators } from "../input";
|
||||
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
|
||||
import attemptInstallByInfoInjectable from "./attempt-install-by-info.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import readFileNotifyInjectable from "./read-file-notify/read-file-notify.injectable";
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
|
||||
export type InstallExtensionFromInput = (input: string) => Promise<void>;
|
||||
|
||||
const installExtensionFromInputInjectable = getInjectable({
|
||||
id: "install-extension-from-input",
|
||||
|
||||
instantiate: (di): InstallExtensionFromInput => {
|
||||
const attemptInstall = di.inject(attemptInstallInjectable);
|
||||
const attemptInstallByInfo = di.inject(attemptInstallByInfoInjectable);
|
||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||
const readFileNotify = di.inject(readFileNotifyInjectable);
|
||||
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
|
||||
return async (input) => {
|
||||
let disposer: ExtendableDisposer | undefined = undefined;
|
||||
|
||||
try {
|
||||
// fixme: improve error messages for non-tar-file URLs
|
||||
if (InputValidators.isUrl.validate(input)) {
|
||||
// install via url
|
||||
disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
||||
const fileName = getBasenameOfPath(input);
|
||||
|
||||
return await attemptInstall({ fileName, dataP: promise }, disposer);
|
||||
}
|
||||
|
||||
try {
|
||||
await InputValidators.isPath.validate(input);
|
||||
|
||||
// install from system path
|
||||
const fileName = getBasenameOfPath(input);
|
||||
|
||||
return await attemptInstall({ fileName, dataP: readFileNotify(input) });
|
||||
} catch (error) {
|
||||
const extNameCaptures = InputValidators.isExtensionNameInstallRegex.captures(input);
|
||||
|
||||
if (extNameCaptures) {
|
||||
const { name, version } = extNameCaptures;
|
||||
|
||||
return await attemptInstallByInfo({ name, version });
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unknown format of input: ${input}`);
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
||||
showErrorNotification((
|
||||
<p>
|
||||
{"Installation has failed: "}
|
||||
<b>{message}</b>
|
||||
</p>
|
||||
));
|
||||
} finally {
|
||||
disposer?.();
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default installExtensionFromInputInjectable;
|
||||
@ -1,23 +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 attemptInstallInjectable from "../attempt-install/attempt-install.injectable";
|
||||
import { installFromInput } from "./install-from-input";
|
||||
import attemptInstallByInfoInjectable from "../attempt-install-by-info.injectable";
|
||||
import extensionInstallationStateStoreInjectable
|
||||
from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
|
||||
const installFromInputInjectable = getInjectable({
|
||||
id: "install-extension-from-input",
|
||||
|
||||
instantiate: (di) =>
|
||||
installFromInput({
|
||||
attemptInstall: di.inject(attemptInstallInjectable),
|
||||
attemptInstallByInfo: di.inject(attemptInstallByInfoInjectable),
|
||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default installFromInputInjectable;
|
||||
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ExtendableDisposer } from "../../../../common/utils";
|
||||
import { downloadFile } from "../../../../common/utils";
|
||||
import { InputValidators } from "../../input";
|
||||
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
||||
import logger from "../../../../main/logger";
|
||||
import { Notifications } from "../../notifications";
|
||||
import path from "path";
|
||||
import React from "react";
|
||||
import { readFileNotify } from "../read-file-notify/read-file-notify";
|
||||
import type { InstallRequest } from "../attempt-install/install-request";
|
||||
import type { ExtensionInfo } from "../attempt-install-by-info.injectable";
|
||||
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
|
||||
|
||||
export type InstallFromInput = (input: string) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
attemptInstall: (request: InstallRequest, disposer?: ExtendableDisposer) => Promise<void>;
|
||||
attemptInstallByInfo: (extensionInfo: ExtensionInfo) => Promise<void>;
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
}
|
||||
|
||||
export const installFromInput = ({
|
||||
attemptInstall,
|
||||
attemptInstallByInfo,
|
||||
extensionInstallationStateStore,
|
||||
}: Dependencies): InstallFromInput => (
|
||||
async (input) => {
|
||||
let disposer: ExtendableDisposer | undefined = undefined;
|
||||
|
||||
try {
|
||||
// fixme: improve error messages for non-tar-file URLs
|
||||
if (InputValidators.isUrl.validate(input)) {
|
||||
// install via url
|
||||
disposer = extensionInstallationStateStore.startPreInstall();
|
||||
const { promise } = downloadFile({ url: input, timeout: 10 * 60 * 1000 });
|
||||
const fileName = path.basename(input);
|
||||
|
||||
return await attemptInstall({ fileName, dataP: promise }, disposer);
|
||||
}
|
||||
|
||||
try {
|
||||
await InputValidators.isPath.validate(input);
|
||||
|
||||
// install from system path
|
||||
const fileName = path.basename(input);
|
||||
|
||||
return await attemptInstall({ fileName, dataP: readFileNotify(input) });
|
||||
} catch (error) {
|
||||
const extNameCaptures = InputValidators.isExtensionNameInstallRegex.captures(input);
|
||||
|
||||
if (extNameCaptures) {
|
||||
const { name, version } = extNameCaptures;
|
||||
|
||||
return await attemptInstallByInfo({ name, version });
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unknown format of input: ${input}`);
|
||||
} catch (error) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: installation has failed: ${message}`, { error, installPath: input });
|
||||
Notifications.error((
|
||||
<p>
|
||||
{"Installation has failed: "}
|
||||
<b>{message}</b>
|
||||
</p>
|
||||
));
|
||||
} finally {
|
||||
disposer?.();
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import readFileBufferInjectable from "../../../../common/fs/read-file-buffer.injectable";
|
||||
|
||||
export type ReadFileNotify = (filePath: string, showError?: boolean) => Promise<Buffer | null>;
|
||||
|
||||
const readFileNotifyInjectable = getInjectable({
|
||||
id: "read-file-notify",
|
||||
instantiate: (di): ReadFileNotify => {
|
||||
const showErrorNotification = di.inject(showErrorNotificationInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const readFileBuffer = di.inject(readFileBufferInjectable);
|
||||
|
||||
return async (filePath, showError = true) => {
|
||||
try {
|
||||
return await readFileBuffer(filePath);
|
||||
} catch (error) {
|
||||
if (showError) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: preloading ${filePath} has failed: ${message}`, { error });
|
||||
showErrorNotification(`Error while reading "${filePath}": ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default readFileNotifyInjectable;
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import fse from "fs-extra";
|
||||
import { getMessageFromError } from "../get-message-from-error/get-message-from-error";
|
||||
import logger from "../../../../main/logger";
|
||||
import { Notifications } from "../../notifications";
|
||||
|
||||
export const readFileNotify = async (filePath: string, showError = true): Promise<Buffer | null> => {
|
||||
try {
|
||||
return await fse.readFile(filePath);
|
||||
} catch (error) {
|
||||
if (showError) {
|
||||
const message = getMessageFromError(error);
|
||||
|
||||
logger.info(`[EXTENSION-INSTALL]: preloading ${filePath} has failed: ${message}`, { error });
|
||||
Notifications.error(`Error while reading "${filePath}": ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -9,7 +9,6 @@ import type { Cluster } from "../../../../common/cluster/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import type { ShowNotification } from "../../notifications";
|
||||
import { resolveTilde } from "../../../utils";
|
||||
import { Icon } from "../../icon";
|
||||
import { PathPicker } from "../../path-picker";
|
||||
import { isWindows } from "../../../../common/vars";
|
||||
@ -17,6 +16,8 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
||||
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
|
||||
import type { ValidateDirectory } from "../../../../common/fs/validate-directory.injectable";
|
||||
import validateDirectoryInjectable from "../../../../common/fs/validate-directory.injectable";
|
||||
import type { ResolveTilde } from "../../../../common/path/resolve-tilde.injectable";
|
||||
import resolveTildeInjectable from "../../../../common/path/resolve-tilde.injectable";
|
||||
|
||||
export interface ClusterLocalTerminalSettingProps {
|
||||
cluster: Cluster;
|
||||
@ -24,9 +25,15 @@ export interface ClusterLocalTerminalSettingProps {
|
||||
interface Dependencies {
|
||||
showErrorNotification: ShowNotification;
|
||||
validateDirectory: ValidateDirectory;
|
||||
resolveTilde: ResolveTilde;
|
||||
}
|
||||
|
||||
const NonInjectedClusterLocalTerminalSetting = observer(({ cluster, showErrorNotification, validateDirectory }: Dependencies & ClusterLocalTerminalSettingProps) => {
|
||||
const NonInjectedClusterLocalTerminalSetting = observer(({
|
||||
cluster,
|
||||
showErrorNotification,
|
||||
validateDirectory,
|
||||
resolveTilde,
|
||||
}: Dependencies & ClusterLocalTerminalSettingProps) => {
|
||||
if (!cluster) {
|
||||
return null;
|
||||
}
|
||||
@ -150,15 +157,11 @@ const NonInjectedClusterLocalTerminalSetting = observer(({ cluster, showErrorNot
|
||||
);
|
||||
});
|
||||
|
||||
export const ClusterLocalTerminalSetting = withInjectables<Dependencies, ClusterLocalTerminalSettingProps>(
|
||||
NonInjectedClusterLocalTerminalSetting,
|
||||
|
||||
{
|
||||
getProps: (di, props) => ({
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
validateDirectory: di.inject(validateDirectoryInjectable),
|
||||
...props,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
export const ClusterLocalTerminalSetting = withInjectables<Dependencies, ClusterLocalTerminalSettingProps>(NonInjectedClusterLocalTerminalSetting, {
|
||||
getProps: (di, props) => ({
|
||||
...props,
|
||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||
validateDirectory: di.inject(validateDirectoryInjectable),
|
||||
resolveTilde: di.inject(resolveTildeInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -3,22 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import { hasCorrectExtension } from "./has-correct-extension";
|
||||
import readFileInjectable from "../../../../common/fs/read-file.injectable";
|
||||
import readDirInjectable from "../../../../common/fs/read-dir.injectable";
|
||||
import readDirectoryInjectable from "../../../../common/fs/read-directory.injectable";
|
||||
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||
import staticFilesDirectoryInjectable from "../../../../common/vars/static-files-directory.injectable";
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
import parsePathInjectable from "../../../../common/path/parse.injectable";
|
||||
|
||||
const lensCreateResourceTemplatesInjectable = getInjectable({
|
||||
id: "lens-create-resource-templates",
|
||||
|
||||
instantiate: async (di): Promise<RawTemplates> => {
|
||||
const readFile = di.inject(readFileInjectable);
|
||||
const readDir = di.inject(readDirInjectable);
|
||||
const readDir = di.inject(readDirectoryInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const staticFilesDirectory = di.inject(staticFilesDirectoryInjectable);
|
||||
const parsePath = di.inject(parsePathInjectable);
|
||||
|
||||
/**
|
||||
* Mapping between file names and their contents
|
||||
@ -28,7 +29,7 @@ const lensCreateResourceTemplatesInjectable = getInjectable({
|
||||
|
||||
for (const dirEntry of await readDir(templatesFolder)) {
|
||||
if (hasCorrectExtension(dirEntry)) {
|
||||
templates.push([path.parse(dirEntry).name, await readFile(path.join(templatesFolder, dirEntry))]);
|
||||
templates.push([parsePath(dirEntry).name, await readFile(joinPaths(templatesFolder, dirEntry))]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,101 +3,108 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IComputedValue } from "mobx";
|
||||
import { computed, observable } from "mobx";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { delay, getOrInsert, isErrnoException, waitForPath } from "../../../utils";
|
||||
import { watch } from "chokidar";
|
||||
import { readFile } from "fs/promises";
|
||||
import logger from "../../../../common/logger";
|
||||
import { hasCorrectExtension } from "./has-correct-extension";
|
||||
import type { RawTemplates } from "./create-resource-templates.injectable";
|
||||
|
||||
const userTemplatesFolder = path.join(os.homedir(), ".k8slens", "templates");
|
||||
|
||||
function groupTemplates(templates: Map<string, string>): RawTemplates[] {
|
||||
const res = new Map<string, [string, string][]>();
|
||||
|
||||
for (const [filePath, contents] of templates) {
|
||||
const rawRelative = path.dirname(path.relative(userTemplatesFolder, filePath));
|
||||
const title = rawRelative === "."
|
||||
? "ungrouped"
|
||||
: rawRelative;
|
||||
|
||||
getOrInsert(res, title, []).push([path.parse(filePath).name, contents]);
|
||||
}
|
||||
|
||||
return [...res.entries()];
|
||||
}
|
||||
|
||||
function watchUserCreateResourceTemplates(): IComputedValue<RawTemplates[]> {
|
||||
/**
|
||||
* Map between filePaths and template contents
|
||||
*/
|
||||
const templates = observable.map<string, string>();
|
||||
|
||||
const onAddOrChange = async (filePath: string) => {
|
||||
if (!hasCorrectExtension(filePath)) {
|
||||
// ignore non yaml or json files
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = await readFile(filePath, "utf-8");
|
||||
|
||||
templates.set(filePath, contents);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
// ignore, file disappeared
|
||||
} else {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while reading ${filePath}`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
const onUnlink = (filePath: string) => {
|
||||
templates.delete(filePath);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
for (let i = 1;; i *= 2) {
|
||||
try {
|
||||
await waitForPath(userTemplatesFolder);
|
||||
break;
|
||||
} catch (error) {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while waiting for ${userTemplatesFolder} to exist, waiting and trying again`, error);
|
||||
await delay(i * 1000); // exponential backoff in seconds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: There is technically a race condition here of the form "time-of-check to time-of-use"
|
||||
*/
|
||||
watch(userTemplatesFolder, {
|
||||
disableGlobbing: true,
|
||||
ignorePermissionErrors: true,
|
||||
usePolling: false,
|
||||
awaitWriteFinish: {
|
||||
pollInterval: 100,
|
||||
stabilityThreshold: 1000,
|
||||
},
|
||||
ignoreInitial: false,
|
||||
atomic: 150, // for "atomic writes"
|
||||
})
|
||||
.on("add", onAddOrChange)
|
||||
.on("change", onAddOrChange)
|
||||
.on("unlink", onUnlink)
|
||||
.on("error", error => {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while watching files under ${userTemplatesFolder}`, error);
|
||||
});
|
||||
})();
|
||||
|
||||
return computed(() => groupTemplates(templates));
|
||||
}
|
||||
import joinPathsInjectable from "../../../../common/path/join-paths.injectable";
|
||||
import watchInjectable from "../../../../common/fs/watch/watch.injectable";
|
||||
import getRelativePathInjectable from "../../../../common/path/get-relative-path.injectable";
|
||||
import homeDirectoryPathInjectable from "../../../../common/os/home-directory-path.injectable";
|
||||
import getDirnameOfPathInjectable from "../../../../common/path/get-dirname.injectable";
|
||||
import loggerInjectable from "../../../../common/logger.injectable";
|
||||
import parsePathInjectable from "../../../../common/path/parse.injectable";
|
||||
|
||||
const userCreateResourceTemplatesInjectable = getInjectable({
|
||||
id: "user-create-resource-templates",
|
||||
instantiate: () => watchUserCreateResourceTemplates(),
|
||||
instantiate: (di) => {
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const watch = di.inject(watchInjectable);
|
||||
const getRelativePath = di.inject(getRelativePathInjectable);
|
||||
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const parsePath = di.inject(parsePathInjectable);
|
||||
|
||||
const userTemplatesFolder = joinPaths(homeDirectoryPath, ".k8slens", "templates");
|
||||
const groupTemplates = (templates: Map<string, string>): RawTemplates[] => {
|
||||
const res = new Map<string, [string, string][]>();
|
||||
|
||||
for (const [filePath, contents] of templates) {
|
||||
const rawRelative = getDirnameOfPath(getRelativePath(userTemplatesFolder, filePath));
|
||||
const title = rawRelative === "."
|
||||
? "ungrouped"
|
||||
: rawRelative;
|
||||
|
||||
getOrInsert(res, title, []).push([parsePath(filePath).name, contents]);
|
||||
}
|
||||
|
||||
return [...res.entries()];
|
||||
};
|
||||
|
||||
/**
|
||||
* Map between filePaths and template contents
|
||||
*/
|
||||
const templates = observable.map<string, string>();
|
||||
|
||||
const onAddOrChange = async (filePath: string) => {
|
||||
if (!hasCorrectExtension(filePath)) {
|
||||
// ignore non yaml or json files
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = await readFile(filePath, "utf-8");
|
||||
|
||||
templates.set(filePath, contents);
|
||||
} catch (error) {
|
||||
if (isErrnoException(error) && error.code === "ENOENT") {
|
||||
// ignore, file disappeared
|
||||
} else {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while reading ${filePath}`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
const onUnlink = (filePath: string) => {
|
||||
templates.delete(filePath);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
for (let i = 1;; i *= 2) {
|
||||
try {
|
||||
await waitForPath(userTemplatesFolder);
|
||||
break;
|
||||
} catch (error) {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while waiting for ${userTemplatesFolder} to exist, waiting and trying again`, error);
|
||||
await delay(i * 1000); // exponential backoff in seconds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: There is technically a race condition here of the form "time-of-check to time-of-use"
|
||||
*/
|
||||
watch(userTemplatesFolder, {
|
||||
disableGlobbing: true,
|
||||
ignorePermissionErrors: true,
|
||||
usePolling: false,
|
||||
awaitWriteFinish: {
|
||||
pollInterval: 100,
|
||||
stabilityThreshold: 1000,
|
||||
},
|
||||
ignoreInitial: false,
|
||||
atomic: 150, // for "atomic writes"
|
||||
})
|
||||
.on("add", onAddOrChange)
|
||||
.on("change", onAddOrChange)
|
||||
.on("unlink", onUnlink)
|
||||
.on("error", error => {
|
||||
logger.warn(`[USER-CREATE-RESOURCE-TEMPLATES]: encountered error while watching files under ${userTemplatesFolder}`, error);
|
||||
});
|
||||
})();
|
||||
|
||||
return computed(() => groupTemplates(templates));
|
||||
},
|
||||
});
|
||||
|
||||
export default userCreateResourceTemplatesInjectable;
|
||||
|
||||
@ -9,6 +9,7 @@ import directoryForKubeConfigsInjectable from "../../common/app-paths/directory-
|
||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
import loadConfigfromFileInjectable from "../../common/kube-helpers/load-config-from-file.injectable";
|
||||
|
||||
const createClusterInjectable = getInjectable({
|
||||
id: "create-cluster",
|
||||
@ -18,6 +19,7 @@ const createClusterInjectable = getInjectable({
|
||||
directoryForKubeConfigs: di.inject(directoryForKubeConfigsInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
||||
|
||||
// TODO: Dismantle wrong abstraction
|
||||
// Note: "as never" to get around strictness in unnatural scenario
|
||||
|
||||
24
yarn.lock
24
yarn.lock
@ -2250,13 +2250,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
||||
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
|
||||
|
||||
"@types/minipass@*":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/minipass/-/minipass-3.1.2.tgz#e2d7f9df0698aff421dcf145b4fc05b8183b9030"
|
||||
integrity sha512-foLGjgrJkUjLG/o2t2ymlZGEoBNBa/TfoUZ7oCTkOjP1T43UGBJspovJou/l3ZuHvye2ewR5cZNtp2zyWgILMA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/mkdirp@^1.0.0":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.2.tgz#8d0bad7aa793abe551860be1f7ae7f3198c16666"
|
||||
@ -2561,13 +2554,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/tar@^4.0.5":
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.5.tgz#5f953f183e36a15c6ce3f336568f6051b7b183f3"
|
||||
integrity sha512-cgwPhNEabHaZcYIy5xeMtux2EmYBitfqEceBUi2t5+ETy4dW6kswt6WX4+HqLeiiKOo42EXbGiDmVJ2x+vi37Q==
|
||||
"@types/tar@^6.1.2":
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.2.tgz#e60108a7d1b08cc91bf2faf1286cc08fdad48bbe"
|
||||
integrity sha512-bnX3RRm70/n1WMwmevdOAeDU4YP7f5JSubgnuU+yrO+xQQjwDboJj3u2NTJI5ngCQhXihqVVAH5h5J8YpdpEvg==
|
||||
dependencies:
|
||||
"@types/minipass" "*"
|
||||
"@types/node" "*"
|
||||
minipass "^3.3.5"
|
||||
|
||||
"@types/tcp-port-used@^1.0.1":
|
||||
version "1.0.1"
|
||||
@ -9108,6 +9101,13 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minipass@^3.3.5:
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.5.tgz#6da7e53a48db8a856eeb9153d85b230a2119e819"
|
||||
integrity sha512-rQ/p+KfKBkeNwo04U15i+hOwoVBVmekmm/HcfTkTN2t9pbQKCMm4eN5gFeqgrrSp/kH/7BYYhTIHOxGqzbBPaA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user