mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge pull request #4653 from lensapp/eliminate-gst-from-app-paths
Eliminate Global Shared State from app paths and relatives
This commit is contained in:
commit
0b321f7144
@ -1,6 +1,7 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ES6PreferShortImport" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@ -12,6 +12,9 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/extensions/pod-menu/node_modules" />
|
<excludeFolder url="file://$MODULE_DIR$/extensions/pod-menu/node_modules" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/static/build" />
|
<excludeFolder url="file://$MODULE_DIR$/static/build" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/extensions/npm/extensions/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/src/extensions/npm/extensions/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/binaries" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
23
__mocks__/electron-updater.ts
Normal file
23
__mocks__/electron-updater.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This mock exists because library causes criminal side-effect on import
|
||||||
|
export const autoUpdater = {};
|
||||||
23
__mocks__/node-pty.ts
Normal file
23
__mocks__/node-pty.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This mock exists because library causes criminal side-effect on import
|
||||||
|
export default {};
|
||||||
@ -370,9 +370,8 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
|
// TODO: Make re-rendering of KubeObjectListLayout not cause namespaceSelector to be closed
|
||||||
|
xit("show logs and highlight the log search entries", async () => {
|
||||||
it("show logs and highlight the log search entries", async () => {
|
|
||||||
await frame.click(`a[href="/workloads"]`);
|
await frame.click(`a[href="/workloads"]`);
|
||||||
await frame.click(`a[href="/pods"]`);
|
await frame.click(`a[href="/pods"]`);
|
||||||
|
|
||||||
@ -417,7 +416,8 @@ utils.describeIf(minikubeReady(TEST_NAMESPACE))("Minikube based tests", () => {
|
|||||||
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
await frame.waitForSelector("div.TableCell >> text='kube-system'");
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
it(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => {
|
// TODO: Make re-rendering of KubeObjectListLayout not cause namespaceSelector to be closed
|
||||||
|
xit(`should create the ${TEST_NAMESPACE} and a pod in the namespace`, async () => {
|
||||||
await frame.click('a[href="/namespaces"]');
|
await frame.click('a[href="/namespaces"]');
|
||||||
await frame.click("button.add-button");
|
await frame.click("button.add-button");
|
||||||
await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'");
|
await frame.waitForSelector("div.AddNamespaceDialog >> text='Create Namespace'");
|
||||||
|
|||||||
@ -195,8 +195,8 @@
|
|||||||
"@hapi/call": "^8.0.1",
|
"@hapi/call": "^8.0.1",
|
||||||
"@hapi/subtext": "^7.0.3",
|
"@hapi/subtext": "^7.0.3",
|
||||||
"@kubernetes/client-node": "^0.16.1",
|
"@kubernetes/client-node": "^0.16.1",
|
||||||
"@ogre-tools/injectable": "2.0.0",
|
"@ogre-tools/injectable": "3.1.1",
|
||||||
"@ogre-tools/injectable-react": "2.0.0",
|
"@ogre-tools/injectable-react": "3.1.1",
|
||||||
"@sentry/electron": "^2.5.4",
|
"@sentry/electron": "^2.5.4",
|
||||||
"@sentry/integrations": "^6.15.0",
|
"@sentry/integrations": "^6.15.0",
|
||||||
"@types/circular-dependency-plugin": "5.0.4",
|
"@types/circular-dependency-plugin": "5.0.4",
|
||||||
|
|||||||
@ -20,29 +20,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { AppPaths } from "../app-paths";
|
|
||||||
import { BaseStore } from "../base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
|
||||||
AppPaths.init();
|
import directoryForUserDataInjectable
|
||||||
|
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
|
||||||
getVersion: () => "99.99.99",
|
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
handle: jest.fn(),
|
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
removeAllListeners: jest.fn(),
|
|
||||||
off: jest.fn(),
|
off: jest.fn(),
|
||||||
send: jest.fn(),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -105,10 +94,17 @@ describe("BaseStore", () => {
|
|||||||
let store: TestStore;
|
let store: TestStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
dis.mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
|
||||||
store = undefined;
|
store = undefined;
|
||||||
TestStore.resetInstance();
|
TestStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-user-data-directory": {
|
||||||
"test-store.json": JSON.stringify({}),
|
"test-store.json": JSON.stringify({}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -130,7 +126,7 @@ describe("BaseStore", () => {
|
|||||||
a: "foo", b: "bar", c: "hello",
|
a: "foo", b: "bar", c: "hello",
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = JSON.parse(readFileSync("tmp/test-store.json").toString());
|
const data = JSON.parse(readFileSync("some-user-data-directory/test-store.json").toString());
|
||||||
|
|
||||||
expect(data).toEqual({ a: "foo", b: "bar", c: "hello" });
|
expect(data).toEqual({ a: "foo", b: "bar", c: "hello" });
|
||||||
});
|
});
|
||||||
@ -153,7 +149,7 @@ describe("BaseStore", () => {
|
|||||||
|
|
||||||
expect(fileSpy).toHaveBeenCalledTimes(2);
|
expect(fileSpy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
const data = JSON.parse(readFileSync("tmp/test-store.json").toString());
|
const data = JSON.parse(readFileSync("some-user-data-directory/test-store.json").toString());
|
||||||
|
|
||||||
expect(data).toEqual({ a: "a", b: "b", c: "" });
|
expect(data).toEqual({ a: "a", b: "b", c: "" });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -24,17 +24,29 @@ import mockFs from "mock-fs";
|
|||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { Cluster } from "../../main/cluster";
|
import type { Cluster } from "../cluster/cluster";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store/cluster-store";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import type { ClusterId } from "../cluster-types";
|
import getCustomKubeConfigDirectoryInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||||
import { getCustomKubeConfigPath } from "../utils";
|
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
|
||||||
import { AppPaths } from "../app-paths";
|
import type { ClusterModel } from "../cluster-types";
|
||||||
|
import type {
|
||||||
|
DependencyInjectionContainer,
|
||||||
|
} from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
|
||||||
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
|
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
const testDataIcon = fs.readFileSync("test-data/cluster-store-migration-icon.png");
|
const testDataIcon = fs.readFileSync(
|
||||||
|
"test-data/cluster-store-migration-icon.png",
|
||||||
|
);
|
||||||
const kubeconfig = `
|
const kubeconfig = `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
clusters:
|
clusters:
|
||||||
@ -59,25 +71,17 @@ users:
|
|||||||
token: kubeconfig-user-q4lm4:xxxyyyy
|
token: kubeconfig-user-q4lm4:xxxyyyy
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function embed(clusterId: ClusterId, contents: any): string {
|
const embed = (directoryName: string, contents: any): string => {
|
||||||
const absPath = getCustomKubeConfigPath(clusterId);
|
fse.ensureDirSync(path.dirname(directoryName));
|
||||||
|
fse.writeFileSync(directoryName, contents, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
|
||||||
fse.ensureDirSync(path.dirname(absPath));
|
return directoryName;
|
||||||
fse.writeFileSync(absPath, contents, { encoding: "utf-8", mode: 0o600 });
|
};
|
||||||
|
|
||||||
return absPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
|
||||||
getVersion: () => "99.99.99",
|
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
ipcMain: {
|
||||||
handle: jest.fn(),
|
handle: jest.fn(),
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
@ -87,21 +91,50 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AppPaths.init();
|
describe("cluster-store", () => {
|
||||||
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
let clusterStore: ClusterStore;
|
||||||
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
|
||||||
describe("empty config", () => {
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mockFs();
|
||||||
|
|
||||||
|
mainDi = dis.mainDi;
|
||||||
|
|
||||||
|
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
|
||||||
|
createCluster = mainDi.inject(createClusterInjectionToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("empty config", () => {
|
||||||
|
let getCustomKubeConfigDirectory: (directoryName: string) => string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
getCustomKubeConfigDirectory = mainDi.inject(
|
||||||
|
getCustomKubeConfigDirectoryInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Remove these by removing Singleton base-class from BaseStore
|
||||||
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
ClusterStore.getInstance(false)?.unregisterIpcListener();
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({}),
|
"lens-cluster-store.json": JSON.stringify({}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -110,32 +143,37 @@ describe("empty config", () => {
|
|||||||
|
|
||||||
describe("with foo cluster added", () => {
|
describe("with foo cluster added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.getInstance().addCluster(
|
const cluster = createCluster({
|
||||||
new Cluster({
|
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "foo",
|
contextName: "foo",
|
||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/tmp",
|
terminalCWD: "/some-directory-for-user-data",
|
||||||
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
icon: "data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||||
clusterName: "minikube",
|
clusterName: "minikube",
|
||||||
},
|
},
|
||||||
kubeConfigPath: embed("foo", kubeconfig),
|
kubeConfigPath: embed(
|
||||||
}),
|
getCustomKubeConfigDirectory("foo"),
|
||||||
);
|
kubeconfig,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterStore.addCluster(cluster);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds new cluster to store", async () => {
|
it("adds new cluster to store", async () => {
|
||||||
const storedCluster = ClusterStore.getInstance().getById("foo");
|
const storedCluster = clusterStore.getById("foo");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("foo");
|
expect(storedCluster.id).toBe("foo");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/tmp");
|
expect(storedCluster.preferences.terminalCWD).toBe("/some-directory-for-user-data");
|
||||||
expect(storedCluster.preferences.icon).toBe("data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5");
|
expect(storedCluster.preferences.icon).toBe(
|
||||||
|
"data:image/jpeg;base64, iVBORw0KGgoAAAANSUhEUgAAA1wAAAKoCAYAAABjkf5",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with prod and dev clusters added", () => {
|
describe("with prod and dev clusters added", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const store = ClusterStore.getInstance();
|
const store = clusterStore;
|
||||||
|
|
||||||
store.addCluster({
|
store.addCluster({
|
||||||
id: "prod",
|
id: "prod",
|
||||||
@ -143,7 +181,10 @@ describe("empty config", () => {
|
|||||||
preferences: {
|
preferences: {
|
||||||
clusterName: "prod",
|
clusterName: "prod",
|
||||||
},
|
},
|
||||||
kubeConfigPath: embed("prod", kubeconfig),
|
kubeConfigPath: embed(
|
||||||
|
getCustomKubeConfigDirectory("prod"),
|
||||||
|
kubeconfig,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
store.addCluster({
|
store.addCluster({
|
||||||
id: "dev",
|
id: "dev",
|
||||||
@ -151,29 +192,33 @@ describe("empty config", () => {
|
|||||||
preferences: {
|
preferences: {
|
||||||
clusterName: "dev",
|
clusterName: "dev",
|
||||||
},
|
},
|
||||||
kubeConfigPath: embed("dev", kubeconfig),
|
kubeConfigPath: embed(
|
||||||
|
getCustomKubeConfigDirectory("dev"),
|
||||||
|
kubeconfig,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("check if store can contain multiple clusters", () => {
|
it("check if store can contain multiple clusters", () => {
|
||||||
expect(ClusterStore.getInstance().hasClusters()).toBeTruthy();
|
expect(clusterStore.hasClusters()).toBeTruthy();
|
||||||
expect(ClusterStore.getInstance().clusters.size).toBe(2);
|
expect(clusterStore.clusters.size).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("check if cluster's kubeconfig file saved", () => {
|
it("check if cluster's kubeconfig file saved", () => {
|
||||||
const file = embed("boo", "kubeconfig");
|
const file = embed(getCustomKubeConfigDirectory("boo"), "kubeconfig");
|
||||||
|
|
||||||
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
|
expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("config with existing clusters", () => {
|
describe("config with existing clusters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"temp-kube-config": kubeconfig,
|
"temp-kube-config": kubeconfig,
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -209,7 +254,7 @@ describe("config with existing clusters", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -217,14 +262,14 @@ describe("config with existing clusters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows to retrieve a cluster", () => {
|
it("allows to retrieve a cluster", () => {
|
||||||
const storedCluster = ClusterStore.getInstance().getById("cluster1");
|
const storedCluster = clusterStore.getById("cluster1");
|
||||||
|
|
||||||
expect(storedCluster.id).toBe("cluster1");
|
expect(storedCluster.id).toBe("cluster1");
|
||||||
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
expect(storedCluster.preferences.terminalCWD).toBe("/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows getting all of the clusters", async () => {
|
it("allows getting all of the clusters", async () => {
|
||||||
const storedClusters = ClusterStore.getInstance().clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(3);
|
expect(storedClusters.length).toBe(3);
|
||||||
expect(storedClusters[0].id).toBe("cluster1");
|
expect(storedClusters[0].id).toBe("cluster1");
|
||||||
@ -233,9 +278,9 @@ describe("config with existing clusters", () => {
|
|||||||
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
|
expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2");
|
||||||
expect(storedClusters[2].id).toBe("cluster3");
|
expect(storedClusters[2].id).toBe("cluster3");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("config with invalid cluster kubeconfig", () => {
|
describe("config with invalid cluster kubeconfig", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const invalidKubeconfig = `
|
const invalidKubeconfig = `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@ -258,10 +303,11 @@ users:
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"invalid-kube-config": invalidKubeconfig,
|
"invalid-kube-config": invalidKubeconfig,
|
||||||
"valid-kube-config": kubeconfig,
|
"valid-kube-config": kubeconfig,
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -283,7 +329,6 @@ users:
|
|||||||
preferences: { terminalCWD: "/foo" },
|
preferences: { terminalCWD: "/foo" },
|
||||||
workspace: "default",
|
workspace: "default",
|
||||||
},
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -291,7 +336,7 @@ users:
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -299,44 +344,18 @@ users:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not enable clusters with invalid kubeconfig", () => {
|
it("does not enable clusters with invalid kubeconfig", () => {
|
||||||
const storedClusters = ClusterStore.getInstance().clustersList;
|
const storedClusters = clusterStore.clustersList;
|
||||||
|
|
||||||
expect(storedClusters.length).toBe(1);
|
expect(storedClusters.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const minimalValidKubeConfig = JSON.stringify({
|
describe("pre 2.0 config with an existing cluster", () => {
|
||||||
apiVersion: "v1",
|
|
||||||
clusters: [{
|
|
||||||
name: "minikube",
|
|
||||||
cluster: {
|
|
||||||
server: "https://192.168.64.3:8443",
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
"current-context": "minikube",
|
|
||||||
contexts: [{
|
|
||||||
context: {
|
|
||||||
cluster: "minikube",
|
|
||||||
user: "minikube",
|
|
||||||
},
|
|
||||||
name: "minikube",
|
|
||||||
}],
|
|
||||||
users: [{
|
|
||||||
name: "minikube",
|
|
||||||
user: {
|
|
||||||
"client-certificate": "/Users/foo/.minikube/client.crt",
|
|
||||||
"client-key": "/Users/foo/.minikube/client.key",
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
kind: "Config",
|
|
||||||
preferences: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 2.0 config with an existing cluster", () => {
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -350,7 +369,7 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -358,17 +377,17 @@ describe("pre 2.0 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[`);
|
expect(fs.readFileSync(config, "utf8")).toContain(`"contexts":[`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -378,40 +397,42 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
cluster1: {
|
cluster1: {
|
||||||
kubeConfig: JSON.stringify({
|
kubeConfig: JSON.stringify({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
clusters: [{
|
clusters: [
|
||||||
|
{
|
||||||
cluster: {
|
cluster: {
|
||||||
server: "https://10.211.55.6:8443",
|
server: "https://10.211.55.6:8443",
|
||||||
},
|
},
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
}],
|
},
|
||||||
contexts: [{
|
],
|
||||||
|
contexts: [
|
||||||
|
{
|
||||||
context: {
|
context: {
|
||||||
cluster: "minikube",
|
cluster: "minikube",
|
||||||
user: "minikube",
|
user: "minikube",
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
},
|
},
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
"current-context": "minikube",
|
"current-context": "minikube",
|
||||||
kind: "Config",
|
kind: "Config",
|
||||||
preferences: {},
|
preferences: {},
|
||||||
users: [{
|
users: [
|
||||||
|
{
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
user: {
|
user: {
|
||||||
"client-certificate": "/Users/foo/.minikube/client.crt",
|
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||||
"client-key": "/Users/foo/.minikube/client.key",
|
"client-key": "/Users/foo/.minikube/client.key",
|
||||||
"auth-provider": {
|
"auth-provider": {
|
||||||
config: {
|
config: {
|
||||||
"access-token": [
|
"access-token": ["should be string"],
|
||||||
"should be string",
|
expiry: ["should be string"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
expiry: [
|
|
||||||
"should be string",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -420,7 +441,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -428,20 +449,24 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("replaces array format access token and expiry into string", async () => {
|
it("replaces array format access token and expiry into string", async () => {
|
||||||
const file = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
const file = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
const config = fs.readFileSync(file, "utf8");
|
const config = fs.readFileSync(file, "utf8");
|
||||||
const kc = yaml.load(config) as Record<string, any>;
|
const kc = yaml.load(config) as Record<string, any>;
|
||||||
|
|
||||||
expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string");
|
expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe(
|
||||||
expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string");
|
"should be string",
|
||||||
|
);
|
||||||
|
expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe(
|
||||||
|
"should be string",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 2.6.0 config with a cluster icon", () => {
|
describe("pre 2.6.0 config with a cluster icon", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -452,17 +477,17 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
kubeConfig: minimalValidKubeConfig,
|
kubeConfig: minimalValidKubeConfig,
|
||||||
icon: "icon_path",
|
icon: "icon_path",
|
||||||
preferences: {
|
preferences: {
|
||||||
terminalCWD: "/tmp",
|
terminalCWD: "/some-directory-for-user-data",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
"icon_path": testDataIcon,
|
icon_path: testDataIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -470,50 +495,19 @@ describe("pre 2.6.0 config with a cluster icon", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("moves the icon into preferences", async () => {
|
it("moves the icon into preferences", async () => {
|
||||||
const storedClusterData = ClusterStore.getInstance().clustersList[0];
|
const storedClusterData = clusterStore.clustersList[0];
|
||||||
|
|
||||||
expect(Object.prototype.hasOwnProperty.call(storedClusterData, "icon")).toBe(false);
|
expect(Object.prototype.hasOwnProperty.call(storedClusterData, "icon")).toBe(false);
|
||||||
expect(Object.prototype.hasOwnProperty.call(storedClusterData.preferences, "icon")).toBe(true);
|
expect(Object.prototype.hasOwnProperty.call(storedClusterData.preferences, "icon")).toBe(true);
|
||||||
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
|
expect(storedClusterData.preferences.icon.startsWith("data:;base64,")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("for a pre 2.7.0-beta.0 config without a workspace", () => {
|
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ClusterStore.resetInstance();
|
ClusterStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
|
||||||
__internal__: {
|
|
||||||
migrations: {
|
|
||||||
version: "2.6.6",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cluster1: {
|
|
||||||
kubeConfig: minimalValidKubeConfig,
|
|
||||||
preferences: {
|
|
||||||
terminalCWD: "/tmp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mockFs(mockOpts);
|
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
ClusterStore.resetInstance();
|
|
||||||
const mockOpts = {
|
|
||||||
"tmp": {
|
|
||||||
"lens-cluster-store.json": JSON.stringify({
|
"lens-cluster-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
@ -531,13 +525,13 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
"icon_path": testDataIcon,
|
icon_path: testDataIcon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
return ClusterStore.createInstance();
|
clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -545,14 +539,48 @@ describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with kubeconfig in a file", async () => {
|
it("migrates to modern format with kubeconfig in a file", async () => {
|
||||||
const config = ClusterStore.getInstance().clustersList[0].kubeConfigPath;
|
const config = clusterStore.clustersList[0].kubeConfigPath;
|
||||||
|
|
||||||
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
expect(fs.readFileSync(config, "utf8")).toBe(minimalValidKubeConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("migrates to modern format with icon not in file", async () => {
|
it("migrates to modern format with icon not in file", async () => {
|
||||||
const { icon } = ClusterStore.getInstance().clustersList[0].preferences;
|
const { icon } = clusterStore.clustersList[0].preferences;
|
||||||
|
|
||||||
expect(icon.startsWith("data:;base64,")).toBe(true);
|
expect(icon.startsWith("data:;base64,")).toBe(true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const minimalValidKubeConfig = JSON.stringify({
|
||||||
|
apiVersion: "v1",
|
||||||
|
clusters: [
|
||||||
|
{
|
||||||
|
name: "minikube",
|
||||||
|
cluster: {
|
||||||
|
server: "https://192.168.64.3:8443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"current-context": "minikube",
|
||||||
|
contexts: [
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
cluster: "minikube",
|
||||||
|
user: "minikube",
|
||||||
|
},
|
||||||
|
name: "minikube",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
users: [
|
||||||
|
{
|
||||||
|
name: "minikube",
|
||||||
|
user: {
|
||||||
|
"client-certificate": "/Users/foo/.minikube/client.crt",
|
||||||
|
"client-key": "/Users/foo/.minikube/client.key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
kind: "Config",
|
||||||
|
preferences: {},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { appEventBus, AppEvent } from "../event-bus";
|
import { appEventBus, AppEvent } from "../app-event-bus/event-bus";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
|
|
||||||
|
|||||||
@ -23,10 +23,11 @@ import { anyObject } from "jest-mock-extended";
|
|||||||
import { merge } from "lodash";
|
import { merge } from "lodash";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { AppPaths } from "../app-paths";
|
|
||||||
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog";
|
||||||
import { ClusterStore } from "../cluster-store";
|
|
||||||
import { HotbarStore } from "../hotbar-store";
|
import { HotbarStore } from "../hotbar-store";
|
||||||
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
jest.mock("../../main/catalog/catalog-entity-registry", () => ({
|
||||||
catalogEntityRegistry: {
|
catalogEntityRegistry: {
|
||||||
@ -109,37 +110,24 @@ const awsCluster = getMockCatalogEntity({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
|
||||||
app: {
|
|
||||||
getVersion: () => "99.99.99",
|
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
handle: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
describe("HotbarStore", () => {
|
describe("HotbarStore", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-hotbar-store.json": JSON.stringify({}),
|
"lens-hotbar-store.json": JSON.stringify({}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
ClusterStore.createInstance();
|
|
||||||
HotbarStore.createInstance();
|
HotbarStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
ClusterStore.resetInstance();
|
|
||||||
HotbarStore.resetInstance();
|
HotbarStore.resetInstance();
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
@ -339,7 +327,7 @@ describe("HotbarStore", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
HotbarStore.resetInstance();
|
HotbarStore.resetInstance();
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"lens-hotbar-store.json": JSON.stringify({
|
"lens-hotbar-store.json": JSON.stringify({
|
||||||
__internal__: {
|
__internal__: {
|
||||||
migrations: {
|
migrations: {
|
||||||
|
|||||||
@ -42,19 +42,40 @@ import { Console } from "console";
|
|||||||
import { SemVer } from "semver";
|
import { SemVer } from "semver";
|
||||||
import electron from "electron";
|
import electron from "electron";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import type { ClusterStoreModel } from "../cluster-store";
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
import { AppPaths } from "../app-paths";
|
import userStoreInjectable from "../user-store/user-store.injectable";
|
||||||
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import type { ClusterStoreModel } from "../cluster-store/cluster-store";
|
||||||
import { defaultTheme } from "../vars";
|
import { defaultTheme } from "../vars";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
describe("user store tests", () => {
|
describe("user store tests", () => {
|
||||||
|
let userStore: UserStore;
|
||||||
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mockFs();
|
||||||
|
|
||||||
|
mainDi = dis.mainDi;
|
||||||
|
|
||||||
|
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
describe("for an empty config", () => {
|
describe("for an empty config", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFs({ tmp: { "config.json": "{}", "kube_config": "{}" }});
|
mockFs({ "some-directory-for-user-data": { "config.json": "{}", "kube_config": "{}" }});
|
||||||
|
|
||||||
(UserStore.createInstance() as any).refreshNewContexts = jest.fn(() => Promise.resolve());
|
userStore = mainDi.inject(userStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -63,46 +84,38 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and retrieving lastSeenAppVersion", () => {
|
it("allows setting and retrieving lastSeenAppVersion", () => {
|
||||||
const us = UserStore.getInstance();
|
userStore.lastSeenAppVersion = "1.2.3";
|
||||||
|
expect(userStore.lastSeenAppVersion).toBe("1.2.3");
|
||||||
us.lastSeenAppVersion = "1.2.3";
|
|
||||||
expect(us.lastSeenAppVersion).toBe("1.2.3");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows setting and getting preferences", () => {
|
it("allows setting and getting preferences", () => {
|
||||||
const us = UserStore.getInstance();
|
userStore.httpsProxy = "abcd://defg";
|
||||||
|
|
||||||
us.httpsProxy = "abcd://defg";
|
expect(userStore.httpsProxy).toBe("abcd://defg");
|
||||||
|
expect(userStore.colorTheme).toBe(defaultTheme);
|
||||||
|
|
||||||
expect(us.httpsProxy).toBe("abcd://defg");
|
userStore.colorTheme = "light";
|
||||||
expect(us.colorTheme).toBe(defaultTheme);
|
expect(userStore.colorTheme).toBe("light");
|
||||||
|
|
||||||
us.colorTheme = "light";
|
|
||||||
expect(us.colorTheme).toBe("light");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly resets theme to default value", async () => {
|
it("correctly resets theme to default value", async () => {
|
||||||
const us = UserStore.getInstance();
|
userStore.colorTheme = "some other theme";
|
||||||
|
userStore.resetTheme();
|
||||||
us.colorTheme = "some other theme";
|
expect(userStore.colorTheme).toBe(defaultTheme);
|
||||||
us.resetTheme();
|
|
||||||
expect(us.colorTheme).toBe(defaultTheme);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("correctly calculates if the last seen version is an old release", () => {
|
it("correctly calculates if the last seen version is an old release", () => {
|
||||||
const us = UserStore.getInstance();
|
expect(userStore.isNewVersion).toBe(true);
|
||||||
|
|
||||||
expect(us.isNewVersion).toBe(true);
|
userStore.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
||||||
|
expect(userStore.isNewVersion).toBe(false);
|
||||||
us.lastSeenAppVersion = (new SemVer(electron.app.getVersion())).inc("major").format();
|
|
||||||
expect(us.isNewVersion).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("migrations", () => {
|
describe("migrations", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockFs({
|
mockFs({
|
||||||
"tmp": {
|
"some-directory-for-user-data": {
|
||||||
"config.json": JSON.stringify({
|
"config.json": JSON.stringify({
|
||||||
user: { username: "foobar" },
|
user: { username: "foobar" },
|
||||||
preferences: { colorTheme: "light" },
|
preferences: { colorTheme: "light" },
|
||||||
@ -112,7 +125,7 @@ describe("user store tests", () => {
|
|||||||
clusters: [
|
clusters: [
|
||||||
{
|
{
|
||||||
id: "foobar",
|
id: "foobar",
|
||||||
kubeConfigPath: "tmp/extension_data/foo/bar",
|
kubeConfigPath: "some-directory-for-user-data/extension_data/foo/bar",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "barfoo",
|
id: "barfoo",
|
||||||
@ -129,7 +142,7 @@ describe("user store tests", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
UserStore.createInstance();
|
userStore = mainDi.inject(userStoreInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -138,16 +151,12 @@ describe("user store tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sets last seen app version to 0.0.0", () => {
|
it("sets last seen app version to 0.0.0", () => {
|
||||||
const us = UserStore.getInstance();
|
expect(userStore.lastSeenAppVersion).toBe("0.0.0");
|
||||||
|
|
||||||
expect(us.lastSeenAppVersion).toBe("0.0.0");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
it.only("skips clusters for adding to kube-sync with files under extension_data/", () => {
|
||||||
const us = UserStore.getInstance();
|
expect(userStore.syncKubeconfigEntries.has("some-directory-for-user-data/extension_data/foo/bar")).toBe(false);
|
||||||
|
expect(userStore.syncKubeconfigEntries.has("some/other/path")).toBe(true);
|
||||||
expect(us.syncKubeconfigEntries.has("tmp/extension_data/foo/bar")).toBe(false);
|
|
||||||
expect(us.syncKubeconfigEntries.has("some/other/path")).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
29
src/common/app-event-bus/app-event-bus.injectable.ts
Normal file
29
src/common/app-event-bus/app-event-bus.injectable.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appEventBus } from "./event-bus";
|
||||||
|
|
||||||
|
const appEventBusInjectable = getInjectable({
|
||||||
|
instantiate: () => appEventBus,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appEventBusInjectable;
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from "./event-emitter";
|
import { EventEmitter } from "../event-emitter";
|
||||||
|
|
||||||
export type AppEvent = {
|
export type AppEvent = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2021 OpenLens Authors
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to
|
|
||||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { app, ipcMain, ipcRenderer } from "electron";
|
|
||||||
import { observable, when } from "mobx";
|
|
||||||
import path from "path";
|
|
||||||
import logger from "./logger";
|
|
||||||
import { fromEntries } from "./utils/objects";
|
|
||||||
import { toJS } from "./utils/toJS";
|
|
||||||
import { isWindows } from "./vars";
|
|
||||||
|
|
||||||
export type PathName = Parameters<typeof app["getPath"]>[0];
|
|
||||||
|
|
||||||
const pathNames: PathName[] = [
|
|
||||||
"home",
|
|
||||||
"appData",
|
|
||||||
"userData",
|
|
||||||
"cache",
|
|
||||||
"temp",
|
|
||||||
"exe",
|
|
||||||
"module",
|
|
||||||
"desktop",
|
|
||||||
"documents",
|
|
||||||
"downloads",
|
|
||||||
"music",
|
|
||||||
"pictures",
|
|
||||||
"videos",
|
|
||||||
"logs",
|
|
||||||
"crashDumps",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isWindows) {
|
|
||||||
pathNames.push("recent");
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AppPaths {
|
|
||||||
private static paths = observable.box<Record<PathName, string> | undefined>();
|
|
||||||
private static readonly ipcChannel = "get-app-paths";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the local copy of the paths from electron.
|
|
||||||
*/
|
|
||||||
static async init(): Promise<void> {
|
|
||||||
logger.info(`[APP-PATHS]: initializing`);
|
|
||||||
|
|
||||||
if (AppPaths.paths.get()) {
|
|
||||||
return void logger.error("[APP-PATHS]: init called more than once");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ipcMain) {
|
|
||||||
AppPaths.initMain();
|
|
||||||
} else {
|
|
||||||
await AppPaths.initRenderer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static initMain(): void {
|
|
||||||
if (process.env.CICD) {
|
|
||||||
app.setPath("appData", process.env.CICD);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.setPath("userData", path.join(app.getPath("appData"), app.getName()));
|
|
||||||
|
|
||||||
const getPath = (pathName: PathName) => {
|
|
||||||
try {
|
|
||||||
return app.getPath(pathName);
|
|
||||||
} catch {
|
|
||||||
logger.debug(`[APP-PATHS] No path found for ${pathName}`);
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
AppPaths.paths.set(fromEntries(pathNames.map(pathName => [pathName, getPath(pathName)] as const).filter(([, path]) => path)));
|
|
||||||
ipcMain.handle(AppPaths.ipcChannel, () => toJS(AppPaths.paths.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async initRenderer(): Promise<void> {
|
|
||||||
const paths = await ipcRenderer.invoke(AppPaths.ipcChannel);
|
|
||||||
|
|
||||||
if (!paths || typeof paths !== "object") {
|
|
||||||
throw Object.assign(new Error("[APP-PATHS]: ipc handler returned unexpected data"), { data: paths });
|
|
||||||
}
|
|
||||||
|
|
||||||
AppPaths.paths.set(paths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An alternative to `app.getPath()` for use in renderer and common.
|
|
||||||
* This function throws if called before initialization.
|
|
||||||
* @param name The name of the path field
|
|
||||||
*/
|
|
||||||
static get(name: PathName): string {
|
|
||||||
if (!AppPaths.paths.get()) {
|
|
||||||
throw new Error("AppPaths.init() has not been called");
|
|
||||||
}
|
|
||||||
|
|
||||||
return AppPaths.paths.get()[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An async version of `AppPaths.get()` which waits for `AppPaths.init()` to
|
|
||||||
* be called before returning
|
|
||||||
*/
|
|
||||||
static async getAsync(name: PathName): Promise<string> {
|
|
||||||
await when(() => Boolean(AppPaths.paths.get()));
|
|
||||||
|
|
||||||
return AppPaths.paths.get()[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
src/common/app-paths/app-path-injection-token.ts
Normal file
31
src/common/app-paths/app-path-injection-token.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { PathName } from "./app-path-names";
|
||||||
|
import { createChannel } from "../ipc-channel/create-channel/create-channel";
|
||||||
|
|
||||||
|
export type AppPaths = Record<PathName, string>;
|
||||||
|
|
||||||
|
export const appPathsInjectionToken = getInjectionToken<AppPaths>();
|
||||||
|
|
||||||
|
export const appPathsIpcChannel = createChannel<AppPaths>("app-paths");
|
||||||
|
|
||||||
|
|
||||||
42
src/common/app-paths/app-path-names.ts
Normal file
42
src/common/app-paths/app-path-names.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { app as electronApp } from "electron";
|
||||||
|
|
||||||
|
export type PathName = Parameters<typeof electronApp["getPath"]>[0];
|
||||||
|
|
||||||
|
export const pathNames: PathName[] = [
|
||||||
|
"home",
|
||||||
|
"appData",
|
||||||
|
"userData",
|
||||||
|
"cache",
|
||||||
|
"temp",
|
||||||
|
"exe",
|
||||||
|
"module",
|
||||||
|
"desktop",
|
||||||
|
"documents",
|
||||||
|
"downloads",
|
||||||
|
"music",
|
||||||
|
"pictures",
|
||||||
|
"videos",
|
||||||
|
"logs",
|
||||||
|
"crashDumps",
|
||||||
|
"recent",
|
||||||
|
];
|
||||||
160
src/common/app-paths/app-paths.test.ts
Normal file
160
src/common/app-paths/app-paths.test.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { DependencyInjectionContainer } from "@ogre-tools/injectable";
|
||||||
|
import { AppPaths, appPathsInjectionToken } from "./app-path-injection-token";
|
||||||
|
import getElectronAppPathInjectable from "../../main/app-paths/get-electron-app-path/get-electron-app-path.injectable";
|
||||||
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import type { PathName } from "./app-path-names";
|
||||||
|
import setElectronAppPathInjectable from "../../main/app-paths/set-electron-app-path/set-electron-app-path.injectable";
|
||||||
|
import appNameInjectable from "../../main/app-paths/app-name/app-name.injectable";
|
||||||
|
import directoryForIntegrationTestingInjectable from "../../main/app-paths/directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
describe("app-paths", () => {
|
||||||
|
let mainDi: DependencyInjectionContainer;
|
||||||
|
let rendererDi: DependencyInjectionContainer;
|
||||||
|
let runSetups: () => Promise<void[]>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mainDi = dis.mainDi;
|
||||||
|
rendererDi = dis.rendererDi;
|
||||||
|
runSetups = dis.runSetups;
|
||||||
|
|
||||||
|
const defaultAppPathsStub: AppPaths = {
|
||||||
|
appData: "some-app-data",
|
||||||
|
cache: "some-cache",
|
||||||
|
crashDumps: "some-crash-dumps",
|
||||||
|
desktop: "some-desktop",
|
||||||
|
documents: "some-documents",
|
||||||
|
downloads: "some-downloads",
|
||||||
|
exe: "some-exe",
|
||||||
|
home: "some-home-path",
|
||||||
|
logs: "some-logs",
|
||||||
|
module: "some-module",
|
||||||
|
music: "some-music",
|
||||||
|
pictures: "some-pictures",
|
||||||
|
recent: "some-recent",
|
||||||
|
temp: "some-temp",
|
||||||
|
videos: "some-videos",
|
||||||
|
userData: "some-irrelevant",
|
||||||
|
};
|
||||||
|
|
||||||
|
mainDi.override(
|
||||||
|
getElectronAppPathInjectable,
|
||||||
|
() =>
|
||||||
|
(key: PathName): string | null =>
|
||||||
|
defaultAppPathsStub[key],
|
||||||
|
);
|
||||||
|
|
||||||
|
mainDi.override(
|
||||||
|
setElectronAppPathInjectable,
|
||||||
|
() =>
|
||||||
|
(key: PathName, path: string): void => {
|
||||||
|
defaultAppPathsStub[key] = path;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
mainDi.override(appNameInjectable, () => "some-app-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("normally", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given in renderer, when injecting app paths, returns application specific app paths", () => {
|
||||||
|
const actual = rendererDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
|
expect(actual).toEqual({
|
||||||
|
appData: "some-app-data",
|
||||||
|
cache: "some-cache",
|
||||||
|
crashDumps: "some-crash-dumps",
|
||||||
|
desktop: "some-desktop",
|
||||||
|
documents: "some-documents",
|
||||||
|
downloads: "some-downloads",
|
||||||
|
exe: "some-exe",
|
||||||
|
home: "some-home-path",
|
||||||
|
logs: "some-logs",
|
||||||
|
module: "some-module",
|
||||||
|
music: "some-music",
|
||||||
|
pictures: "some-pictures",
|
||||||
|
recent: "some-recent",
|
||||||
|
temp: "some-temp",
|
||||||
|
videos: "some-videos",
|
||||||
|
userData: `some-app-data${path.sep}some-app-name`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given in main, when injecting app paths, returns application specific app paths", () => {
|
||||||
|
const actual = mainDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
|
expect(actual).toEqual({
|
||||||
|
appData: "some-app-data",
|
||||||
|
cache: "some-cache",
|
||||||
|
crashDumps: "some-crash-dumps",
|
||||||
|
desktop: "some-desktop",
|
||||||
|
documents: "some-documents",
|
||||||
|
downloads: "some-downloads",
|
||||||
|
exe: "some-exe",
|
||||||
|
home: "some-home-path",
|
||||||
|
logs: "some-logs",
|
||||||
|
module: "some-module",
|
||||||
|
music: "some-music",
|
||||||
|
pictures: "some-pictures",
|
||||||
|
recent: "some-recent",
|
||||||
|
temp: "some-temp",
|
||||||
|
videos: "some-videos",
|
||||||
|
userData: `some-app-data${path.sep}some-app-name`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when running integration tests", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
mainDi.override(
|
||||||
|
directoryForIntegrationTestingInjectable,
|
||||||
|
() => "some-integration-testing-app-data",
|
||||||
|
);
|
||||||
|
|
||||||
|
await runSetups();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given in renderer, when injecting path for app data, has integration specific app data path", () => {
|
||||||
|
const { appData, userData } = rendererDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
|
expect({ appData, userData }).toEqual({
|
||||||
|
appData: "some-integration-testing-app-data",
|
||||||
|
userData: `some-integration-testing-app-data${path.sep}some-app-name`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given in main, when injecting path for app data, has integration specific app data path", () => {
|
||||||
|
const { appData, userData } = rendererDi.inject(appPathsInjectionToken);
|
||||||
|
|
||||||
|
expect({ appData, userData }).toEqual({
|
||||||
|
appData: "some-integration-testing-app-data",
|
||||||
|
userData: `some-integration-testing-app-data${path.sep}some-app-name`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -18,16 +18,15 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import * as uuid from "uuid";
|
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import { AppPaths } from "../app-paths";
|
|
||||||
import type { ClusterId } from "../cluster-types";
|
|
||||||
|
|
||||||
export function storedKubeConfigFolder(): string {
|
const directoryForBinariesInjectable = getInjectable({
|
||||||
return path.resolve(AppPaths.get("userData"), "kubeconfigs");
|
instantiate: (di) =>
|
||||||
}
|
path.join(di.inject(directoryForUserDataInjectable), "binaries"),
|
||||||
|
|
||||||
export function getCustomKubeConfigPath(clusterId: ClusterId = uuid.v4()): string {
|
lifecycle: lifecycleEnum.singleton,
|
||||||
return path.resolve(storedKubeConfigFolder(), clusterId);
|
});
|
||||||
}
|
|
||||||
|
export default directoryForBinariesInjectable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionToken } from "../app-path-injection-token";
|
||||||
|
|
||||||
|
const directoryForDownloadsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(appPathsInjectionToken).downloads,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForDownloadsInjectable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionToken } from "../app-path-injection-token";
|
||||||
|
|
||||||
|
const directoryForExesInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(appPathsInjectionToken).exe,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForExesInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const directoryForKubeConfigsInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
path.resolve(di.inject(directoryForUserDataInjectable), "kubeconfigs"),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForKubeConfigsInjectable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionToken } from "../app-path-injection-token";
|
||||||
|
|
||||||
|
const directoryForTempInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(appPathsInjectionToken).temp,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForTempInjectable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appPathsInjectionToken } from "../app-path-injection-token";
|
||||||
|
|
||||||
|
const directoryForUserDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(appPathsInjectionToken).userData,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForUserDataInjectable;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import path from "path";
|
||||||
|
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
|
|
||||||
|
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
||||||
|
instantiate: (di) => (directoryName: string) => {
|
||||||
|
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||||
|
|
||||||
|
return path.resolve(
|
||||||
|
directoryForKubeConfigs,
|
||||||
|
directoryName,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getCustomKubeConfigDirectoryInjectable;
|
||||||
@ -30,7 +30,9 @@ import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
|||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
import { isTestEnv } from "./vars";
|
import { isTestEnv } from "./vars";
|
||||||
import { kebabCase } from "lodash";
|
import { kebabCase } from "lodash";
|
||||||
import { AppPaths } from "./app-paths";
|
import { getLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "./app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||||
syncOptions?: {
|
syncOptions?: {
|
||||||
@ -102,7 +104,9 @@ export abstract class BaseStore<T> extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected cwd() {
|
protected cwd() {
|
||||||
return AppPaths.get("userData");
|
const di = getLegacyGlobalDiForExtensionApi();
|
||||||
|
|
||||||
|
return di.inject(directoryForUserDataInjectable);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saveToFile(model: T) {
|
protected saveToFile(model: T) {
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
import { catalogCategoryRegistry } from "../catalog/catalog-category-registry";
|
||||||
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog";
|
import { CatalogEntity, CatalogEntityActionContext, CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus, CatalogCategory, CatalogCategorySpec } from "../catalog";
|
||||||
import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
import { clusterActivateHandler, clusterDisconnectHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store/cluster-store";
|
||||||
import { broadcastMessage, requestMain } from "../ipc";
|
import { broadcastMessage, requestMain } from "../ipc";
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
import type { CatalogEntitySpec } from "../catalog/catalog-entity";
|
||||||
|
|||||||
34
src/common/cluster-store/cluster-store.injectable.ts
Normal file
34
src/common/cluster-store/cluster-store.injectable.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ClusterStore } from "./cluster-store";
|
||||||
|
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
|
||||||
|
|
||||||
|
const clusterStoreInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
ClusterStore.createInstance({
|
||||||
|
createCluster: di.inject(createClusterInjectionToken),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default clusterStoreInjectable;
|
||||||
@ -19,16 +19,17 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { ipcMain, ipcRenderer, webFrame } from "electron";
|
import { ipcMain, ipcRenderer, webFrame } from "electron";
|
||||||
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
||||||
import { BaseStore } from "./base-store";
|
import { BaseStore } from "../base-store";
|
||||||
import { Cluster } from "../main/cluster";
|
import { Cluster } from "../cluster/cluster";
|
||||||
import migrations from "../migrations/cluster-store";
|
import migrations from "../../migrations/cluster-store";
|
||||||
import logger from "../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { appEventBus } from "./event-bus";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
import { ipcMainHandle, requestMain } from "./ipc";
|
import { ipcMainHandle, requestMain } from "../ipc";
|
||||||
import { disposer, toJS } from "./utils";
|
import { disposer, toJS } from "../utils";
|
||||||
import type { ClusterModel, ClusterId, ClusterState } from "./cluster-types";
|
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
clusters?: ClusterModel[];
|
clusters?: ClusterModel[];
|
||||||
@ -36,13 +37,17 @@ export interface ClusterStoreModel {
|
|||||||
|
|
||||||
const initialStates = "cluster:states";
|
const initialStates = "cluster:states";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
createCluster: (model: ClusterModel) => Cluster
|
||||||
|
}
|
||||||
|
|
||||||
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||||
readonly displayName = "ClusterStore";
|
readonly displayName = "ClusterStore";
|
||||||
clusters = observable.map<ClusterId, Cluster>();
|
clusters = observable.map<ClusterId, Cluster>();
|
||||||
|
|
||||||
protected disposer = disposer();
|
protected disposer = disposer();
|
||||||
|
|
||||||
constructor() {
|
constructor(private dependencies: Dependencies) {
|
||||||
super({
|
super({
|
||||||
configName: "lens-cluster-store",
|
configName: "lens-cluster-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -123,7 +128,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
const cluster = clusterOrModel instanceof Cluster
|
const cluster = clusterOrModel instanceof Cluster
|
||||||
? clusterOrModel
|
? clusterOrModel
|
||||||
: new Cluster(clusterOrModel);
|
: this.dependencies.createCluster(clusterOrModel);
|
||||||
|
|
||||||
this.clusters.set(cluster.id, cluster);
|
this.clusters.set(cluster.id, cluster);
|
||||||
|
|
||||||
@ -143,7 +148,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
if (cluster) {
|
if (cluster) {
|
||||||
cluster.updateModel(clusterModel);
|
cluster.updateModel(clusterModel);
|
||||||
} else {
|
} else {
|
||||||
cluster = new Cluster(clusterModel);
|
cluster = this.dependencies.createCluster(clusterModel);
|
||||||
}
|
}
|
||||||
newClusters.set(clusterModel.id, cluster);
|
newClusters.set(clusterModel.id, cluster);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { getHostedClusterId } from "../../utils";
|
||||||
|
import clusterStoreInjectable from "../cluster-store.injectable";
|
||||||
|
|
||||||
|
const hostedClusterInjectable = getInjectable({
|
||||||
|
instantiate: (di) => {
|
||||||
|
const hostedClusterId = getHostedClusterId();
|
||||||
|
|
||||||
|
return di.inject(clusterStoreInjectable).getById(hostedClusterId);
|
||||||
|
},
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hostedClusterInjectable;
|
||||||
@ -21,22 +21,29 @@
|
|||||||
|
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
|
import { action, comparer, computed, makeObservable, observable, reaction, when } from "mobx";
|
||||||
import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc";
|
import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../ipc";
|
||||||
import { ContextHandler } from "./context-handler";
|
import type { ContextHandler } from "../../main/context-handler/context-handler";
|
||||||
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { Kubectl } from "./kubectl";
|
import type { Kubectl } from "../../main/kubectl/kubectl";
|
||||||
import { KubeconfigManager } from "./kubeconfig-manager";
|
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
||||||
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../common/kube-helpers";
|
import { loadConfigFromFile, loadConfigFromFileSync, validateKubeConfig } from "../kube-helpers";
|
||||||
import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../common/rbac";
|
import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../rbac";
|
||||||
import logger from "./logger";
|
import logger from "../../main/logger";
|
||||||
import { VersionDetector } from "./cluster-detectors/version-detector";
|
import { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||||
import { DetectorRegistry } from "./cluster-detectors/detector-registry";
|
import { DetectorRegistry } from "../../main/cluster-detectors/detector-registry";
|
||||||
import plimit from "p-limit";
|
import plimit from "p-limit";
|
||||||
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../common/cluster-types";
|
import type { ClusterState, ClusterRefreshOptions, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate } from "../cluster-types";
|
||||||
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../common/cluster-types";
|
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus } from "../cluster-types";
|
||||||
import { disposer, storedKubeConfigFolder, toJS } from "../common/utils";
|
import { disposer, toJS } from "../utils";
|
||||||
import type { Response } from "request";
|
import type { Response } from "request";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
directoryForKubeConfigs: string,
|
||||||
|
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager,
|
||||||
|
createContextHandler: (cluster: Cluster) => ContextHandler,
|
||||||
|
createKubectl: (clusterVersion: string) => Kubectl
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cluster
|
* Cluster
|
||||||
*
|
*
|
||||||
@ -221,7 +228,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
return this.preferences.defaultNamespace;
|
return this.preferences.defaultNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(private dependencies: Dependencies, model: ClusterModel) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.id = model.id;
|
this.id = model.id;
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
@ -237,8 +244,8 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
|
|
||||||
if (ipcMain) {
|
if (ipcMain) {
|
||||||
// for the time being, until renderer gets its own cluster type
|
// for the time being, until renderer gets its own cluster type
|
||||||
this.contextHandler = new ContextHandler(this);
|
this.contextHandler = this.dependencies.createContextHandler(this);
|
||||||
this.proxyKubeconfigManager = new KubeconfigManager(this, this.contextHandler);
|
this.proxyKubeconfigManager = this.dependencies.createKubeconfigManager(this);
|
||||||
|
|
||||||
logger.debug(`[CLUSTER]: Cluster init success`, {
|
logger.debug(`[CLUSTER]: Cluster init success`, {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -362,7 +369,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async ensureKubectl() {
|
async ensureKubectl() {
|
||||||
this.kubeCtl ??= new Kubectl(this.version);
|
this.kubeCtl ??= this.dependencies.createKubectl(this.version);
|
||||||
|
|
||||||
await this.kubeCtl.ensureKubectl();
|
await this.kubeCtl.ensureKubectl();
|
||||||
|
|
||||||
@ -719,6 +726,6 @@ export class Cluster implements ClusterModel, ClusterState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isInLocalKubeconfig() {
|
isInLocalKubeconfig() {
|
||||||
return this.kubeConfigPath.startsWith(storedKubeConfigFolder());
|
return this.kubeConfigPath.startsWith(this.dependencies.directoryForKubeConfigs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
src/common/cluster/create-cluster-injection-token.ts
Normal file
26
src/common/cluster/create-cluster-injection-token.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import type { ClusterModel } from "../cluster-types";
|
||||||
|
import type { Cluster } from "./cluster";
|
||||||
|
|
||||||
|
export const createClusterInjectionToken =
|
||||||
|
getInjectionToken<(model: ClusterModel) => Cluster>();
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import path from "path";
|
||||||
|
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
|
const directoryForLensLocalStorageInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
path.resolve(
|
||||||
|
di.inject(directoryForUserDataInjectable),
|
||||||
|
"lens-local-storage",
|
||||||
|
),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForLensLocalStorageInjectable;
|
||||||
@ -45,7 +45,7 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
this.listeners.length = 0;
|
this.listeners.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(...data: D) {
|
emit = (...data: D) => {
|
||||||
for (const [callback, { once }] of this.listeners) {
|
for (const [callback, { once }] of this.listeners) {
|
||||||
if (once) {
|
if (once) {
|
||||||
this.removeListener(callback);
|
this.removeListener(callback);
|
||||||
@ -55,5 +55,5 @@ export class EventEmitter<D extends [...any[]]> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/common/fs/fs.injectable.ts
Normal file
30
src/common/fs/fs.injectable.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import fse from "fs-extra";
|
||||||
|
|
||||||
|
const fsInjectable = getInjectable({
|
||||||
|
instantiate: () => fse,
|
||||||
|
causesSideEffects: true,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default fsInjectable;
|
||||||
33
src/common/fs/read-json-file/read-json-file.injectable.ts
Normal file
33
src/common/fs/read-json-file/read-json-file.injectable.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { readJsonFile } from "./read-json-file";
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import fsInjectable from "../fs.injectable";
|
||||||
|
|
||||||
|
const readJsonFileInjectable = getInjectable({
|
||||||
|
instantiate: (di) => readJsonFile({
|
||||||
|
fs: di.inject(fsInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default readJsonFileInjectable;
|
||||||
32
src/common/fs/read-json-file/read-json-file.ts
Normal file
32
src/common/fs/read-json-file/read-json-file.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { JsonObject } from "type-fest";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
fs: {
|
||||||
|
readJson: (filePath: string) => Promise<JsonObject>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const readJsonFile =
|
||||||
|
({ fs }: Dependencies) =>
|
||||||
|
(filePath: string) =>
|
||||||
|
fs.readJson(filePath);
|
||||||
30
src/common/fs/write-json-file/write-json-file.injectable.ts
Normal file
30
src/common/fs/write-json-file/write-json-file.injectable.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { writeJsonFile } from "./write-json-file";
|
||||||
|
import fsInjectable from "../fs.injectable";
|
||||||
|
|
||||||
|
const writeJsonFileInjectable = getInjectable({
|
||||||
|
instantiate: (di) => writeJsonFile({ fs: di.inject(fsInjectable) }),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default writeJsonFileInjectable;
|
||||||
47
src/common/fs/write-json-file/write-json-file.ts
Normal file
47
src/common/fs/write-json-file/write-json-file.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import path from "path";
|
||||||
|
import type { JsonObject } from "type-fest";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
fs: {
|
||||||
|
ensureDir: (
|
||||||
|
directoryName: string,
|
||||||
|
options: { mode: number }
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
writeJson: (
|
||||||
|
filePath: string,
|
||||||
|
contentObject: JsonObject,
|
||||||
|
options: { spaces: number }
|
||||||
|
) => Promise<void>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const writeJsonFile =
|
||||||
|
({ fs }: Dependencies) =>
|
||||||
|
async (filePath: string, contentObject: JsonObject) => {
|
||||||
|
const directoryName = path.dirname(filePath);
|
||||||
|
|
||||||
|
await fs.ensureDir(directoryName, { mode: 0o755 });
|
||||||
|
|
||||||
|
await fs.writeJson(filePath, contentObject, { spaces: 2 });
|
||||||
|
};
|
||||||
24
src/common/ipc-channel/channel.d.ts
vendored
Normal file
24
src/common/ipc-channel/channel.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
export interface Channel<TInstance> {
|
||||||
|
name: string;
|
||||||
|
_template: TInstance;
|
||||||
|
}
|
||||||
26
src/common/ipc-channel/create-channel/create-channel.ts
Normal file
26
src/common/ipc-channel/create-channel/create-channel.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { Channel } from "../channel";
|
||||||
|
|
||||||
|
export const createChannel = <TInstance>(name: string): Channel<TInstance> => ({
|
||||||
|
name,
|
||||||
|
_template: null,
|
||||||
|
});
|
||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Cluster } from "../../main/cluster";
|
import type { Cluster } from "../cluster/cluster";
|
||||||
|
|
||||||
export interface ClusterContext {
|
export interface ClusterContext {
|
||||||
cluster?: Cluster;
|
cluster?: Cluster;
|
||||||
|
|||||||
@ -27,11 +27,11 @@ import type { KubeJsonApiData } from "../kube-json-api";
|
|||||||
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
import { isClusterPageContext } from "../../utils/cluster-id-url-parsing";
|
||||||
|
|
||||||
export class PodsApi extends KubeApi<Pod> {
|
export class PodsApi extends KubeApi<Pod> {
|
||||||
async getLogs(params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> {
|
getLogs = async (params: { namespace: string; name: string }, query?: IPodLogsQuery): Promise<string> => {
|
||||||
const path = `${this.getUrl(params)}/log`;
|
const path = `${this.getUrl(params)}/log`;
|
||||||
|
|
||||||
return this.request.get(path, { query });
|
return this.request.get(path, { query });
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
|
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<IPodMetrics> {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { JsonApi } from "./json-api";
|
|||||||
import { KubeJsonApi } from "./kube-json-api";
|
import { KubeJsonApi } from "./kube-json-api";
|
||||||
import { apiKubePrefix, apiPrefix, isDebugging, isDevelopment } from "../../common/vars";
|
import { apiKubePrefix, apiPrefix, isDebugging, isDevelopment } from "../../common/vars";
|
||||||
import { isClusterPageContext } from "../utils/cluster-id-url-parsing";
|
import { isClusterPageContext } from "../utils/cluster-id-url-parsing";
|
||||||
import { appEventBus } from "../event-bus";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
|
|
||||||
let apiBase: JsonApi;
|
let apiBase: JsonApi;
|
||||||
let apiKube: KubeJsonApi;
|
let apiKube: KubeJsonApi;
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { apiBase, apiKube } from "./index";
|
|||||||
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
||||||
import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
import { KubeObjectConstructor, KubeObject, KubeStatus } from "./kube-object";
|
||||||
import byline from "byline";
|
import byline from "byline";
|
||||||
import type { IKubeWatchEvent } from "./kube-watch-api";
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||||
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
import { KubeJsonApi, KubeJsonApiData } from "./kube-json-api";
|
||||||
import { noop } from "../utils";
|
import { noop } from "../utils";
|
||||||
import type { RequestInit } from "node-fetch";
|
import type { RequestInit } from "node-fetch";
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import type { ClusterContext } from "./cluster-context";
|
|||||||
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
|
import { action, computed, makeObservable, observable, reaction, when } from "mobx";
|
||||||
import { autoBind, noop, rejectPromiseBy } from "../utils";
|
import { autoBind, noop, rejectPromiseBy } from "../utils";
|
||||||
import { KubeObject, KubeStatus } from "./kube-object";
|
import { KubeObject, KubeStatus } from "./kube-object";
|
||||||
import type { IKubeWatchEvent } from "./kube-watch-api";
|
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||||
import { ItemStore } from "../item.store";
|
import { ItemStore } from "../item.store";
|
||||||
import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./kube-api";
|
import { ensureObjectSelfLink, IKubeApiQueryParams, KubeApi } from "./kube-api";
|
||||||
import { parseKubeApi } from "./kube-api-parse";
|
import { parseKubeApi } from "./kube-api-parse";
|
||||||
@ -101,6 +101,7 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
return KubeObjectStore.defaultContext.get();
|
return KubeObjectStore.defaultContext.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Circular dependency: KubeObjectStore -> ClusterFrameContext -> NamespaceStore -> KubeObjectStore
|
||||||
@computed get contextItems(): T[] {
|
@computed get contextItems(): T[] {
|
||||||
const namespaces = this.context?.contextNamespaces ?? [];
|
const namespaces = this.context?.contextNamespaces ?? [];
|
||||||
|
|
||||||
@ -327,14 +328,14 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
|||||||
return this.api.create(params, data);
|
return this.api.create(params, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> {
|
create = async (params: { name: string; namespace?: string }, data?: Partial<T>): Promise<T> => {
|
||||||
const newItem = await this.createItem(params, data);
|
const newItem = await this.createItem(params, data);
|
||||||
const items = this.sortItems([...this.items, newItem]);
|
const items = this.sortItems([...this.items, newItem]);
|
||||||
|
|
||||||
this.items.replace(items);
|
this.items.replace(items);
|
||||||
|
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
};
|
||||||
|
|
||||||
private postUpdate(rawItem: KubeJsonApiData): T {
|
private postUpdate(rawItem: KubeJsonApiData): T {
|
||||||
const newItem = new this.api.objectConstructor(rawItem);
|
const newItem = new this.api.objectConstructor(rawItem);
|
||||||
|
|||||||
28
src/common/k8s-api/kube-watch-event.d.ts
vendored
Normal file
28
src/common/k8s-api/kube-watch-event.d.ts
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { KubeJsonApiData } from "./kube-json-api";
|
||||||
|
|
||||||
|
export interface IKubeWatchEvent<T extends KubeJsonApiData> {
|
||||||
|
type: "ADDED" | "MODIFIED" | "DELETED" | "ERROR";
|
||||||
|
object?: T;
|
||||||
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ import logger from "../../main/logger";
|
|||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { requestMain } from "../ipc";
|
import { requestMain } from "../ipc";
|
||||||
import { clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../cluster-ipc";
|
import { clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../cluster-ipc";
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store/cluster-store";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { productName } from "../vars";
|
import { productName } from "../vars";
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,8 @@ import { pathToRegexp } from "path-to-regexp";
|
|||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import type Url from "url-parse";
|
import type Url from "url-parse";
|
||||||
import { RoutingError, RoutingErrorType } from "./error";
|
import { RoutingError, RoutingErrorType } from "./error";
|
||||||
import { ExtensionsStore } from "../../extensions/extensions-store";
|
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
|
||||||
import type { ExtensionLoader as ExtensionLoaderType } from "../../extensions/extension-loader/extension-loader";
|
import type { ExtensionLoader } from "../../extensions/extension-loader";
|
||||||
import type { LensExtension } from "../../extensions/lens-extension";
|
import type { LensExtension } from "../../extensions/lens-extension";
|
||||||
import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler";
|
import type { RouteHandler, RouteParams } from "../../extensions/registries/protocol-handler";
|
||||||
import { when } from "mobx";
|
import { when } from "mobx";
|
||||||
@ -79,7 +79,8 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
extensionLoader: ExtensionLoaderType
|
extensionLoader: ExtensionLoader
|
||||||
|
extensionsStore: ExtensionsStore
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class LensProtocolRouter {
|
export abstract class LensProtocolRouter {
|
||||||
@ -212,7 +213,7 @@ export abstract class LensProtocolRouter {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ExtensionsStore.getInstance().isEnabled(extension)) {
|
if (!this.dependencies.extensionsStore.isEnabled(extension)) {
|
||||||
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -18,19 +18,13 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { UserStore } from "./user-store";
|
||||||
|
|
||||||
import { createStorage } from "../../utils";
|
const userStoreInjectable = getInjectable({
|
||||||
|
instantiate: () => UserStore.createInstance(),
|
||||||
|
|
||||||
export interface SidebarStorageState {
|
lifecycle: lifecycleEnum.singleton,
|
||||||
width: number;
|
|
||||||
expanded: {
|
|
||||||
[itemId: string]: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultSidebarWidth = 200;
|
|
||||||
|
|
||||||
export const sidebarStorage = createStorage<SidebarStorageState>("sidebar", {
|
|
||||||
width: defaultSidebarWidth,
|
|
||||||
expanded: {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default userStoreInjectable;
|
||||||
@ -26,12 +26,10 @@ import { BaseStore } from "../base-store";
|
|||||||
import migrations, { fileNameMigration } from "../../migrations/user-store";
|
import migrations, { fileNameMigration } from "../../migrations/user-store";
|
||||||
import { getAppVersion } from "../utils/app-version";
|
import { getAppVersion } from "../utils/app-version";
|
||||||
import { kubeConfigDefaultPath } from "../kube-helpers";
|
import { kubeConfigDefaultPath } from "../kube-helpers";
|
||||||
import { appEventBus } from "../event-bus";
|
import { appEventBus } from "../app-event-bus/event-bus";
|
||||||
import path from "path";
|
|
||||||
import { ObservableToggleSet, toJS } from "../../renderer/utils";
|
import { ObservableToggleSet, toJS } from "../../renderer/utils";
|
||||||
import { DESCRIPTORS, EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers";
|
import { DESCRIPTORS, EditorConfiguration, ExtensionRegistry, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { AppPaths } from "../app-paths";
|
|
||||||
|
|
||||||
export interface UserStoreModel {
|
export interface UserStoreModel {
|
||||||
lastSeenAppVersion: string;
|
lastSeenAppVersion: string;
|
||||||
@ -233,11 +231,3 @@ export class UserStore extends BaseStore<UserStoreModel> /* implements UserStore
|
|||||||
return toJS(model);
|
return toJS(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Getting default directory to download kubectl binaries
|
|
||||||
* @returns string
|
|
||||||
*/
|
|
||||||
export function getDefaultKubectlDownloadPath(): string {
|
|
||||||
return path.join(AppPaths.get("userData"), "binaries");
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ClusterStore } from "../cluster-store";
|
import { ClusterStore } from "../cluster-store/cluster-store";
|
||||||
import type { KubeResource } from "../rbac";
|
import type { KubeResource } from "../rbac";
|
||||||
import { getHostedClusterId } from "./cluster-id-url-parsing";
|
import { getHostedClusterId } from "./cluster-id-url-parsing";
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,6 @@ export * from "./extended-map";
|
|||||||
export * from "./formatDuration";
|
export * from "./formatDuration";
|
||||||
export * from "./getRandId";
|
export * from "./getRandId";
|
||||||
export * from "./hash-set";
|
export * from "./hash-set";
|
||||||
export * from "./local-kubeconfig";
|
|
||||||
export * from "./n-fircate";
|
export * from "./n-fircate";
|
||||||
export * from "./objects";
|
export * from "./objects";
|
||||||
export * from "./openExternal";
|
export * from "./openExternal";
|
||||||
|
|||||||
@ -20,12 +20,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionLoader } from "../extension-loader";
|
import type { ExtensionLoader } from "../extension-loader";
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
import { ExtensionsStore } from "../extensions-store";
|
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
|
||||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import updateExtensionsStateInjectable
|
||||||
|
from "../extension-loader/update-extensions-state/update-extensions-state.injectable";
|
||||||
|
import { getDisForUnitTesting } from "../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -33,15 +35,6 @@ const manifestPath = "manifest/path";
|
|||||||
const manifestPath2 = "manifest/path2";
|
const manifestPath2 = "manifest/path2";
|
||||||
const manifestPath3 = "manifest/path3";
|
const manifestPath3 = "manifest/path3";
|
||||||
|
|
||||||
jest.mock("../extensions-store", () => ({
|
|
||||||
ExtensionsStore: {
|
|
||||||
getInstance: () => ({
|
|
||||||
whenLoaded: Promise.resolve(true),
|
|
||||||
mergeState: jest.fn(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
"electron",
|
"electron",
|
||||||
() => ({
|
() => ({
|
||||||
@ -131,14 +124,27 @@ jest.mock(
|
|||||||
|
|
||||||
describe("ExtensionLoader", () => {
|
describe("ExtensionLoader", () => {
|
||||||
let extensionLoader: ExtensionLoader;
|
let extensionLoader: ExtensionLoader;
|
||||||
|
let updateExtensionStateMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
const di = getDiForUnitTesting();
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
mockFs();
|
||||||
|
|
||||||
|
updateExtensionStateMock = jest.fn();
|
||||||
|
|
||||||
|
dis.mainDi.override(updateExtensionsStateInjectable, () => updateExtensionStateMock);
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
|
||||||
|
extensionLoader = dis.mainDi.inject(extensionLoaderInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only("renderer updates extension after ipc broadcast", async done => {
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renderer updates extension after ipc broadcast", async done => {
|
||||||
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`);
|
||||||
|
|
||||||
await extensionLoader.init();
|
await extensionLoader.init();
|
||||||
@ -177,26 +183,26 @@ describe("ExtensionLoader", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
it("updates ExtensionsStore after isEnabled is changed", async () => {
|
||||||
(ExtensionsStore.getInstance().mergeState as any).mockClear();
|
|
||||||
|
|
||||||
// Disable sending events in this test
|
|
||||||
(ipcRenderer.on as any).mockImplementation();
|
|
||||||
|
|
||||||
await extensionLoader.init();
|
await extensionLoader.init();
|
||||||
|
|
||||||
expect(ExtensionsStore.getInstance().mergeState).not.toHaveBeenCalled();
|
expect(updateExtensionStateMock).not.toHaveBeenCalled();
|
||||||
|
|
||||||
Array.from(extensionLoader.userExtensions.values())[0].isEnabled = false;
|
runInAction(() => {
|
||||||
|
extensionLoader.setIsEnabled("manifest/path", false);
|
||||||
|
});
|
||||||
|
|
||||||
expect(ExtensionsStore.getInstance().mergeState).toHaveBeenCalledWith({
|
expect(updateExtensionStateMock).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
"manifest/path": {
|
"manifest/path": {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
name: "TestExtension",
|
name: "TestExtension",
|
||||||
},
|
},
|
||||||
|
|
||||||
"manifest/path2": {
|
"manifest/path2": {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
name: "TestExtension2",
|
name: "TestExtension2",
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,14 +20,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getAppVersion } from "../../common/utils";
|
import { getAppVersion } from "../../common/utils";
|
||||||
import { ExtensionsStore } from "../extensions-store";
|
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||||
|
import getEnabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable";
|
||||||
import * as Preferences from "./user-preferences";
|
import * as Preferences from "./user-preferences";
|
||||||
|
|
||||||
export const version = getAppVersion();
|
export const version = getAppVersion();
|
||||||
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
export { isSnap, isWindows, isMac, isLinux, appName, slackUrl, issuesTrackerUrl } from "../../common/vars";
|
||||||
|
|
||||||
export function getEnabledExtensions(): string[] {
|
export const getEnabledExtensions = asLegacyGlobalFunctionForExtensionApi(getEnabledExtensionsInjectable);
|
||||||
return ExtensionsStore.getInstance().enabledExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Preferences };
|
export { Preferences };
|
||||||
|
|||||||
@ -19,5 +19,5 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { appEventBus } from "../../common/event-bus";
|
export { appEventBus } from "../../common/app-event-bus/event-bus";
|
||||||
export type { AppEvent } from "../../common/event-bus";
|
export type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
|
||||||
|
|
||||||
|
const getEnabledExtensionsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => () =>
|
||||||
|
di.inject(extensionsStoreInjectable).enabledExtensions,
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getEnabledExtensionsInjectable;
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ExtensionDiscovery } from "./extension-discovery";
|
||||||
|
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||||
|
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
||||||
|
import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable";
|
||||||
|
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
||||||
|
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
||||||
|
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||||
|
import extensionPackageRootDirectoryInjectable from "../extension-installer/extension-package-root-directory/extension-package-root-directory.injectable";
|
||||||
|
import installExtensionsInjectable from "../extension-installer/install-extensions/install-extensions.injectable";
|
||||||
|
|
||||||
|
const extensionDiscoveryInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
new ExtensionDiscovery({
|
||||||
|
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||||
|
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||||
|
|
||||||
|
extensionInstallationStateStore: di.inject(
|
||||||
|
extensionInstallationStateStoreInjectable,
|
||||||
|
),
|
||||||
|
|
||||||
|
isCompatibleBundledExtension: di.inject(
|
||||||
|
isCompatibleBundledExtensionInjectable,
|
||||||
|
),
|
||||||
|
|
||||||
|
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
||||||
|
|
||||||
|
installExtension: di.inject(installExtensionInjectable),
|
||||||
|
installExtensions: di.inject(installExtensionsInjectable),
|
||||||
|
|
||||||
|
extensionPackageRootDirectory: di.inject(
|
||||||
|
extensionPackageRootDirectoryInjectable,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionDiscoveryInjectable;
|
||||||
@ -20,16 +20,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { watch } from "chokidar";
|
import { watch } from "chokidar";
|
||||||
import { ExtensionsStore } from "../extensions-store";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ExtensionDiscovery } from "../extension-discovery";
|
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { AppPaths } from "../../common/app-paths";
|
|
||||||
import type { ExtensionLoader } from "../extension-loader";
|
|
||||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
|
||||||
import * as fse from "fs-extra";
|
import * as fse from "fs-extra";
|
||||||
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
|
import extensionDiscoveryInjectable from "../extension-discovery/extension-discovery.injectable";
|
||||||
|
import type { ExtensionDiscovery } from "../extension-discovery/extension-discovery";
|
||||||
|
import installExtensionInjectable
|
||||||
|
from "../extension-installer/install-extension/install-extension.injectable";
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
jest.setTimeout(60_000);
|
jest.setTimeout(60_000);
|
||||||
|
|
||||||
@ -37,12 +39,7 @@ jest.mock("../../common/ipc");
|
|||||||
jest.mock("chokidar", () => ({
|
jest.mock("chokidar", () => ({
|
||||||
watch: jest.fn(),
|
watch: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock("../extension-installer", () => ({
|
|
||||||
extensionInstaller: {
|
|
||||||
extensionPackagesRoot: "",
|
|
||||||
installPackage: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
jest.mock("fs-extra");
|
jest.mock("fs-extra");
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -60,23 +57,28 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
const mockedWatch = watch as jest.MockedFunction<typeof watch>;
|
||||||
const mockedFse = fse as jest.Mocked<typeof fse>;
|
const mockedFse = fse as jest.Mocked<typeof fse>;
|
||||||
|
|
||||||
describe("ExtensionDiscovery", () => {
|
describe("ExtensionDiscovery", () => {
|
||||||
let extensionLoader: ExtensionLoader;
|
let extensionDiscovery: ExtensionDiscovery;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
ExtensionDiscovery.resetInstance();
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
ExtensionsStore.resetInstance();
|
|
||||||
ExtensionsStore.createInstance();
|
|
||||||
|
|
||||||
const di = getDiForUnitTesting();
|
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
|
||||||
|
di.override(installExtensionInjectable, () => () => Promise.resolve());
|
||||||
|
|
||||||
extensionLoader = di.inject(extensionLoaderInjectable);
|
mockFs();
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
|
extensionDiscovery = di.inject(extensionDiscoveryInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("emits add for added extension", async (done) => {
|
it("emits add for added extension", async (done) => {
|
||||||
@ -106,9 +108,6 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any,
|
(mockWatchInstance) as any,
|
||||||
);
|
);
|
||||||
const extensionDiscovery = ExtensionDiscovery.createInstance(
|
|
||||||
extensionLoader,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
@ -118,7 +117,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
extensionDiscovery.events.on("add", extension => {
|
extensionDiscovery.events.on("add", extension => {
|
||||||
expect(extension).toEqual({
|
expect(extension).toEqual({
|
||||||
absolutePath: expect.any(String),
|
absolutePath: expect.any(String),
|
||||||
id: path.normalize("node_modules/my-extension/package.json"),
|
id: path.normalize("some-directory-for-user-data/node_modules/my-extension/package.json"),
|
||||||
isBundled: false,
|
isBundled: false,
|
||||||
isEnabled: false,
|
isEnabled: false,
|
||||||
isCompatible: false,
|
isCompatible: false,
|
||||||
@ -126,7 +125,7 @@ describe("ExtensionDiscovery", () => {
|
|||||||
name: "my-extension",
|
name: "my-extension",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
},
|
},
|
||||||
manifestPath: path.normalize("node_modules/my-extension/package.json"),
|
manifestPath: path.normalize("some-directory-for-user-data/node_modules/my-extension/package.json"),
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -150,9 +149,6 @@ describe("ExtensionDiscovery", () => {
|
|||||||
mockedWatch.mockImplementationOnce(() =>
|
mockedWatch.mockImplementationOnce(() =>
|
||||||
(mockWatchInstance) as any,
|
(mockWatchInstance) as any,
|
||||||
);
|
);
|
||||||
const extensionDiscovery = ExtensionDiscovery.createInstance(
|
|
||||||
extensionLoader,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Need to force isLoaded to be true so that the file watching is started
|
// Need to force isLoaded to be true so that the file watching is started
|
||||||
extensionDiscovery.isLoaded = true;
|
extensionDiscovery.isLoaded = true;
|
||||||
@ -23,19 +23,37 @@ import { watch } from "chokidar";
|
|||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { observable, reaction, when, makeObservable } from "mobx";
|
import { makeObservable, observable, reaction, when } from "mobx";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { broadcastMessage, ipcMainHandle, ipcRendererOn, requestMain } from "../common/ipc";
|
import {
|
||||||
import { Singleton, toJS } from "../common/utils";
|
broadcastMessage,
|
||||||
import logger from "../main/logger";
|
ipcMainHandle,
|
||||||
import { ExtensionInstallationStateStore } from "../renderer/components/+extensions/extension-install.store";
|
ipcRendererOn,
|
||||||
import { extensionInstaller } from "./extension-installer";
|
requestMain,
|
||||||
import { ExtensionsStore } from "./extensions-store";
|
} from "../../common/ipc";
|
||||||
import type { ExtensionLoader } from "./extension-loader";
|
import { toJS } from "../../common/utils";
|
||||||
import type { LensExtensionId, LensExtensionManifest } from "./lens-extension";
|
import logger from "../../main/logger";
|
||||||
import { isProduction } from "../common/vars";
|
import type { ExtensionsStore } from "../extensions-store/extensions-store";
|
||||||
import { isCompatibleBundledExtension, isCompatibleExtension } from "./extension-compatibility";
|
import type { ExtensionLoader } from "../extension-loader";
|
||||||
|
import type { LensExtensionId, LensExtensionManifest } from "../lens-extension";
|
||||||
|
import { isProduction } from "../../common/vars";
|
||||||
|
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
|
||||||
|
import type { PackageJson } from "type-fest";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
extensionLoader: ExtensionLoader;
|
||||||
|
extensionsStore: ExtensionsStore;
|
||||||
|
|
||||||
|
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||||
|
|
||||||
|
isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean;
|
||||||
|
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
||||||
|
|
||||||
|
installExtension: (name: string) => Promise<void>;
|
||||||
|
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>
|
||||||
|
extensionPackageRootDirectory: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface InstalledExtension {
|
export interface InstalledExtension {
|
||||||
id: LensExtensionId;
|
id: LensExtensionId;
|
||||||
@ -81,7 +99,7 @@ interface LoadFromFolderOptions {
|
|||||||
* - "add": When extension is added. The event is of type InstalledExtension
|
* - "add": When extension is added. The event is of type InstalledExtension
|
||||||
* - "remove": When extension is removed. The event is of type LensExtensionId
|
* - "remove": When extension is removed. The event is of type LensExtensionId
|
||||||
*/
|
*/
|
||||||
export class ExtensionDiscovery extends Singleton {
|
export class ExtensionDiscovery {
|
||||||
protected bundledFolderPath: string;
|
protected bundledFolderPath: string;
|
||||||
|
|
||||||
private loadStarted = false;
|
private loadStarted = false;
|
||||||
@ -99,9 +117,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
|
|
||||||
public events = new EventEmitter();
|
public events = new EventEmitter();
|
||||||
|
|
||||||
constructor(protected extensionLoader: ExtensionLoader) {
|
constructor(protected dependencies : Dependencies) {
|
||||||
super();
|
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,11 +126,11 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get packageJsonPath(): string {
|
get packageJsonPath(): string {
|
||||||
return path.join(extensionInstaller.extensionPackagesRoot, manifestFilename);
|
return path.join(this.dependencies.extensionPackageRootDirectory, manifestFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
get inTreeTargetPath(): string {
|
get inTreeTargetPath(): string {
|
||||||
return path.join(extensionInstaller.extensionPackagesRoot, "extensions");
|
return path.join(this.dependencies.extensionPackageRootDirectory, "extensions");
|
||||||
}
|
}
|
||||||
|
|
||||||
get inTreeFolderPath(): string {
|
get inTreeFolderPath(): string {
|
||||||
@ -122,7 +138,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get nodeModulesPath(): string {
|
get nodeModulesPath(): string {
|
||||||
return path.join(extensionInstaller.extensionPackagesRoot, "node_modules");
|
return path.join(this.dependencies.extensionPackageRootDirectory, "node_modules");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,7 +213,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
|
|
||||||
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
if (path.basename(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
|
||||||
try {
|
try {
|
||||||
ExtensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
this.dependencies.extensionInstallationStateStore.setInstallingFromMain(manifestPath);
|
||||||
const absPath = path.dirname(manifestPath);
|
const absPath = path.dirname(manifestPath);
|
||||||
|
|
||||||
// this.loadExtensionFromPath updates this.packagesJson
|
// this.loadExtensionFromPath updates this.packagesJson
|
||||||
@ -208,7 +224,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
await fse.remove(extension.manifestPath);
|
await fse.remove(extension.manifestPath);
|
||||||
|
|
||||||
// Install dependencies for the new extension
|
// Install dependencies for the new extension
|
||||||
await this.installPackage(extension.absolutePath);
|
await this.dependencies.installExtension(extension.absolutePath);
|
||||||
|
|
||||||
this.extensions.set(extension.id, extension);
|
this.extensions.set(extension.id, extension);
|
||||||
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
logger.info(`${logModule} Added extension ${extension.manifest.name}`);
|
||||||
@ -217,7 +233,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
logger.error(`${logModule}: failed to add extension: ${error}`, { error });
|
||||||
} finally {
|
} finally {
|
||||||
ExtensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
this.dependencies.extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -277,7 +293,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
* @param extensionId The ID of the extension to uninstall.
|
* @param extensionId The ID of the extension to uninstall.
|
||||||
*/
|
*/
|
||||||
async uninstallExtension(extensionId: LensExtensionId): Promise<void> {
|
async uninstallExtension(extensionId: LensExtensionId): Promise<void> {
|
||||||
const { manifest, absolutePath } = this.extensions.get(extensionId) ?? this.extensionLoader.getExtension(extensionId);
|
const { manifest, absolutePath } = this.extensions.get(extensionId) ?? this.dependencies.extensionLoader.getExtension(extensionId);
|
||||||
|
|
||||||
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
logger.info(`${logModule} Uninstalling ${manifest.name}`);
|
||||||
|
|
||||||
@ -295,10 +311,12 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
|
|
||||||
this.loadStarted = true;
|
this.loadStarted = true;
|
||||||
|
|
||||||
logger.info(`${logModule} loading extensions from ${extensionInstaller.extensionPackagesRoot}`);
|
logger.info(
|
||||||
|
`${logModule} loading extensions from ${this.dependencies.extensionPackageRootDirectory}`,
|
||||||
|
);
|
||||||
|
|
||||||
// fs.remove won't throw if path is missing
|
// fs.remove won't throw if path is missing
|
||||||
await fse.remove(path.join(extensionInstaller.extensionPackagesRoot, "package-lock.json"));
|
await fse.remove(path.join(this.dependencies.extensionPackageRootDirectory, "package-lock.json"));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Verify write access to static/extensions, which is needed for symlinking
|
// Verify write access to static/extensions, which is needed for symlinking
|
||||||
@ -357,11 +375,11 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
try {
|
try {
|
||||||
const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
|
const manifest = await fse.readJson(manifestPath) as LensExtensionManifest;
|
||||||
const id = this.getInstalledManifestPath(manifest.name);
|
const id = this.getInstalledManifestPath(manifest.name);
|
||||||
const isEnabled = ExtensionsStore.getInstance().isEnabled({ id, isBundled });
|
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
|
||||||
const extensionDir = path.dirname(manifestPath);
|
const extensionDir = path.dirname(manifestPath);
|
||||||
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||||
const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
const absolutePath = (isProduction && await fse.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||||
const isCompatible = (isBundled && isCompatibleBundledExtension(manifest)) || isCompatibleExtension(manifest);
|
const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@ -394,7 +412,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
for (const extension of userExtensions) {
|
for (const extension of userExtensions) {
|
||||||
if ((await fse.pathExists(extension.manifestPath)) === false) {
|
if ((await fse.pathExists(extension.manifestPath)) === false) {
|
||||||
try {
|
try {
|
||||||
await this.installPackage(extension.absolutePath);
|
await this.dependencies.installExtension(extension.absolutePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error.message || error || "unknown error";
|
const message = error.message || error || "unknown error";
|
||||||
const { name, version } = extension.manifest;
|
const { name, version } = extension.manifest;
|
||||||
@ -417,11 +435,7 @@ export class ExtensionDiscovery extends Singleton {
|
|||||||
extensions.map(extension => [extension.manifest.name, extension.absolutePath]),
|
extensions.map(extension => [extension.manifest.name, extension.absolutePath]),
|
||||||
);
|
);
|
||||||
|
|
||||||
return extensionInstaller.installPackages(packageJsonPath, { dependencies });
|
return this.dependencies.installExtensions(packageJsonPath, { dependencies });
|
||||||
}
|
|
||||||
|
|
||||||
async installPackage(name: string): Promise<void> {
|
|
||||||
return extensionInstaller.installPackage(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadBundledExtensions(): Promise<InstalledExtension[]> {
|
async loadBundledExtensions(): Promise<InstalledExtension[]> {
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appSemVer } from "../../../common/vars";
|
||||||
|
import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension";
|
||||||
|
|
||||||
|
const isCompatibleBundledExtensionInjectable = getInjectable({
|
||||||
|
instantiate: () => isCompatibleBundledExtension({ appSemVer }),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isCompatibleBundledExtensionInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { LensExtensionManifest } from "../../lens-extension";
|
||||||
|
import { isProduction } from "../../../common/vars";
|
||||||
|
import type { SemVer } from "semver";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
appSemVer: SemVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isCompatibleBundledExtension =
|
||||||
|
({ appSemVer }: Dependencies) =>
|
||||||
|
(manifest: LensExtensionManifest): boolean =>
|
||||||
|
!isProduction || manifest.version === appSemVer.raw;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { appSemVer } from "../../../common/vars";
|
||||||
|
import { isCompatibleExtension } from "./is-compatible-extension";
|
||||||
|
|
||||||
|
const isCompatibleExtensionInjectable = getInjectable({
|
||||||
|
instantiate: () => isCompatibleExtension({ appSemVer }),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default isCompatibleExtensionInjectable;
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import semver, { SemVer } from "semver";
|
||||||
|
import type { LensExtensionManifest } from "../../lens-extension";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
appSemVer: SemVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isCompatibleExtension = ({
|
||||||
|
appSemVer,
|
||||||
|
}: Dependencies): ((manifest: LensExtensionManifest) => boolean) => {
|
||||||
|
const { major, minor, patch, prerelease: oldPrelease } = appSemVer;
|
||||||
|
let prerelease = "";
|
||||||
|
|
||||||
|
if (oldPrelease.length > 0) {
|
||||||
|
const [first] = oldPrelease;
|
||||||
|
|
||||||
|
if (first === "alpha" || first === "beta" || first === "rc") {
|
||||||
|
/**
|
||||||
|
* Strip the build IDs and "latest" prerelease tag as that is not really
|
||||||
|
* a part of API version
|
||||||
|
*/
|
||||||
|
prerelease = `-${oldPrelease.slice(0, 2).join(".")}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We unfortunately have to format as string because the constructor only
|
||||||
|
* takes an instance or a string.
|
||||||
|
*/
|
||||||
|
const strippedVersion = new SemVer(
|
||||||
|
`${major}.${minor}.${patch}${prerelease}`,
|
||||||
|
{ includePrerelease: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (manifest: LensExtensionManifest): boolean => {
|
||||||
|
if (manifest.engines?.lens) {
|
||||||
|
/**
|
||||||
|
* include Lens's prerelease tag in the matching so the extension's
|
||||||
|
* compatibility is not limited by it
|
||||||
|
*/
|
||||||
|
return semver.satisfies(strippedVersion, manifest.engines.lens, {
|
||||||
|
includePrerelease: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ExtensionInstallationStateStore } from "./extension-installation-state-store";
|
||||||
|
|
||||||
|
const extensionInstallationStateStoreInjectable = getInjectable({
|
||||||
|
instantiate: () => new ExtensionInstallationStateStore(),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionInstallationStateStoreInjectable;
|
||||||
@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { action, computed, observable } from "mobx";
|
||||||
|
import logger from "../../main/logger";
|
||||||
|
import { disposer } from "../../renderer/utils";
|
||||||
|
import type { ExtendableDisposer } from "../../renderer/utils";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
import { broadcastMessage } from "../../common/ipc";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
export enum ExtensionInstallationState {
|
||||||
|
INSTALLING = "installing",
|
||||||
|
UNINSTALLING = "uninstalling",
|
||||||
|
IDLE = "idle",
|
||||||
|
}
|
||||||
|
|
||||||
|
const Prefix = "[ExtensionInstallationStore]";
|
||||||
|
|
||||||
|
export class ExtensionInstallationStateStore {
|
||||||
|
private InstallingFromMainChannel =
|
||||||
|
"extension-installation-state-store:install";
|
||||||
|
|
||||||
|
private ClearInstallingFromMainChannel =
|
||||||
|
"extension-installation-state-store:clear-install";
|
||||||
|
|
||||||
|
private PreInstallIds = observable.set<string>();
|
||||||
|
private UninstallingExtensions = observable.set<string>();
|
||||||
|
private InstallingExtensions = observable.set<string>();
|
||||||
|
|
||||||
|
bindIpcListeners = () => {
|
||||||
|
ipcRenderer
|
||||||
|
.on(this.InstallingFromMainChannel, (event, extId) => {
|
||||||
|
this.setInstalling(extId);
|
||||||
|
})
|
||||||
|
|
||||||
|
.on(this.ClearInstallingFromMainChannel, (event, extId) => {
|
||||||
|
this.clearInstalling(extId);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strictly transitions an extension from not installing to installing
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
* @throws if state is not IDLE
|
||||||
|
*/
|
||||||
|
@action setInstalling = (extId: string): void => {
|
||||||
|
logger.debug(`${Prefix}: trying to set ${extId} as installing`);
|
||||||
|
|
||||||
|
const curState = this.getInstallationState(extId);
|
||||||
|
|
||||||
|
if (curState !== ExtensionInstallationState.IDLE) {
|
||||||
|
throw new Error(
|
||||||
|
`${Prefix}: cannot set ${extId} as installing. Is currently ${curState}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.InstallingExtensions.add(extId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts that an extension is being installed by the main process
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
*/
|
||||||
|
setInstallingFromMain = (extId: string): void => {
|
||||||
|
broadcastMessage(this.InstallingFromMainChannel, extId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts that an extension is no longer being installed by the main process
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
*/
|
||||||
|
clearInstallingFromMain = (extId: string): void => {
|
||||||
|
broadcastMessage(this.ClearInstallingFromMainChannel, extId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the start of a pre-install phase of an extension installation. The
|
||||||
|
* part of the installation before the tarball has been unpacked and the ID
|
||||||
|
* determined.
|
||||||
|
* @returns a disposer which should be called to mark the end of the install phase
|
||||||
|
*/
|
||||||
|
@action startPreInstall = (): ExtendableDisposer => {
|
||||||
|
const preInstallStepId = uuid.v4();
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
|
||||||
|
);
|
||||||
|
this.PreInstallIds.add(preInstallStepId);
|
||||||
|
|
||||||
|
return disposer(() => {
|
||||||
|
this.PreInstallIds.delete(preInstallStepId);
|
||||||
|
logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strictly transitions an extension from not uninstalling to uninstalling
|
||||||
|
* @param extId the ID of the extension
|
||||||
|
* @throws if state is not IDLE
|
||||||
|
*/
|
||||||
|
@action setUninstalling = (extId: string): void => {
|
||||||
|
logger.debug(`${Prefix}: trying to set ${extId} as uninstalling`);
|
||||||
|
|
||||||
|
const curState = this.getInstallationState(extId);
|
||||||
|
|
||||||
|
if (curState !== ExtensionInstallationState.IDLE) {
|
||||||
|
throw new Error(
|
||||||
|
`${Prefix}: cannot set ${extId} as uninstalling. Is currently ${curState}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UninstallingExtensions.add(extId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strictly clears the INSTALLING state of an extension
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
* @throws if state is not INSTALLING
|
||||||
|
*/
|
||||||
|
@action clearInstalling = (extId: string): void => {
|
||||||
|
logger.debug(`${Prefix}: trying to clear ${extId} as installing`);
|
||||||
|
|
||||||
|
const curState = this.getInstallationState(extId);
|
||||||
|
|
||||||
|
switch (curState) {
|
||||||
|
case ExtensionInstallationState.INSTALLING:
|
||||||
|
return void this.InstallingExtensions.delete(extId);
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`${Prefix}: cannot clear INSTALLING state for ${extId}, it is currently ${curState}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strictly clears the UNINSTALLING state of an extension
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
* @throws if state is not UNINSTALLING
|
||||||
|
*/
|
||||||
|
@action clearUninstalling = (extId: string): void => {
|
||||||
|
logger.debug(`${Prefix}: trying to clear ${extId} as uninstalling`);
|
||||||
|
|
||||||
|
const curState = this.getInstallationState(extId);
|
||||||
|
|
||||||
|
switch (curState) {
|
||||||
|
case ExtensionInstallationState.UNINSTALLING:
|
||||||
|
return void this.UninstallingExtensions.delete(extId);
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`${Prefix}: cannot clear UNINSTALLING state for ${extId}, it is currently ${curState}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current state of the extension. IDLE is default value.
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
*/
|
||||||
|
getInstallationState = (extId: string): ExtensionInstallationState => {
|
||||||
|
if (this.InstallingExtensions.has(extId)) {
|
||||||
|
return ExtensionInstallationState.INSTALLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.UninstallingExtensions.has(extId)) {
|
||||||
|
return ExtensionInstallationState.UNINSTALLING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtensionInstallationState.IDLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the extension is currently INSTALLING
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
*/
|
||||||
|
isExtensionInstalling = (extId: string): boolean =>
|
||||||
|
this.getInstallationState(extId) === ExtensionInstallationState.INSTALLING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the extension is currently UNINSTALLING
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
*/
|
||||||
|
isExtensionUninstalling = (extId: string): boolean =>
|
||||||
|
this.getInstallationState(extId) ===
|
||||||
|
ExtensionInstallationState.UNINSTALLING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the extension is currently IDLE
|
||||||
|
* @param extId The ID of the extension
|
||||||
|
*/
|
||||||
|
isExtensionIdle = (extId: string): boolean =>
|
||||||
|
this.getInstallationState(extId) === ExtensionInstallationState.IDLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current number of extensions installing
|
||||||
|
*/
|
||||||
|
@computed get installing(): number {
|
||||||
|
return this.InstallingExtensions.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current number of extensions uninstalling
|
||||||
|
*/
|
||||||
|
get uninstalling(): number {
|
||||||
|
return this.UninstallingExtensions.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one extension currently installing
|
||||||
|
*/
|
||||||
|
get anyInstalling(): boolean {
|
||||||
|
return this.installing > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one extension currently uninstalling
|
||||||
|
*/
|
||||||
|
get anyUninstalling(): boolean {
|
||||||
|
return this.uninstalling > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current number of extensions preinstalling
|
||||||
|
*/
|
||||||
|
get preinstalling(): number {
|
||||||
|
return this.PreInstallIds.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one extension currently downloading
|
||||||
|
*/
|
||||||
|
get anyPreinstalling(): boolean {
|
||||||
|
return this.preinstalling > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is at least one installing or preinstalling step taking place
|
||||||
|
*/
|
||||||
|
get anyPreInstallingOrInstalling(): boolean {
|
||||||
|
return this.anyInstalling || this.anyPreinstalling;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ExtensionInstaller } from "./extension-installer";
|
||||||
|
import extensionPackageRootDirectoryInjectable from "./extension-package-root-directory/extension-package-root-directory.injectable";
|
||||||
|
|
||||||
|
const extensionInstallerInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
new ExtensionInstaller({
|
||||||
|
extensionPackageRootDirectory: di.inject(
|
||||||
|
extensionPackageRootDirectoryInjectable,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionInstallerInjectable;
|
||||||
@ -23,12 +23,14 @@ import AwaitLock from "await-lock";
|
|||||||
import child_process from "child_process";
|
import child_process from "child_process";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import logger from "../main/logger";
|
import logger from "../../main/logger";
|
||||||
import { extensionPackagesRoot } from "./extension-loader";
|
|
||||||
import type { PackageJson } from "type-fest";
|
import type { PackageJson } from "type-fest";
|
||||||
|
|
||||||
const logModule = "[EXTENSION-INSTALLER]";
|
const logModule = "[EXTENSION-INSTALLER]";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
extensionPackageRootDirectory: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs dependencies for extensions
|
* Installs dependencies for extensions
|
||||||
@ -36,9 +38,7 @@ const logModule = "[EXTENSION-INSTALLER]";
|
|||||||
export class ExtensionInstaller {
|
export class ExtensionInstaller {
|
||||||
private installLock = new AwaitLock();
|
private installLock = new AwaitLock();
|
||||||
|
|
||||||
get extensionPackagesRoot() {
|
constructor(private dependencies: Dependencies) {}
|
||||||
return extensionPackagesRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
get npmPath() {
|
get npmPath() {
|
||||||
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
||||||
@ -47,7 +47,7 @@ export class ExtensionInstaller {
|
|||||||
/**
|
/**
|
||||||
* Write package.json to the file system and execute npm install for it.
|
* Write package.json to the file system and execute npm install for it.
|
||||||
*/
|
*/
|
||||||
async installPackages(packageJsonPath: string, packagesJson: PackageJson): Promise<void> {
|
installPackages = async (packageJsonPath: string, packagesJson: PackageJson): Promise<void> => {
|
||||||
// Mutual exclusion to install packages in sequence
|
// Mutual exclusion to install packages in sequence
|
||||||
await this.installLock.acquireAsync();
|
await this.installLock.acquireAsync();
|
||||||
|
|
||||||
@ -57,34 +57,34 @@ export class ExtensionInstaller {
|
|||||||
mode: 0o600,
|
mode: 0o600,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`${logModule} installing dependencies at ${extensionPackagesRoot()}`);
|
logger.info(`${logModule} installing dependencies at ${this.dependencies.extensionPackageRootDirectory}`);
|
||||||
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"]);
|
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock"]);
|
||||||
logger.info(`${logModule} dependencies installed at ${extensionPackagesRoot()}`);
|
logger.info(`${logModule} dependencies installed at ${this.dependencies.extensionPackageRootDirectory}`);
|
||||||
} finally {
|
} finally {
|
||||||
this.installLock.release();
|
this.installLock.release();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install single package using npm
|
* Install single package using npm
|
||||||
*/
|
*/
|
||||||
async installPackage(name: string): Promise<void> {
|
installPackage = async (name: string): Promise<void> => {
|
||||||
// Mutual exclusion to install packages in sequence
|
// Mutual exclusion to install packages in sequence
|
||||||
await this.installLock.acquireAsync();
|
await this.installLock.acquireAsync();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(`${logModule} installing package from ${name} to ${extensionPackagesRoot()}`);
|
logger.info(`${logModule} installing package from ${name} to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||||
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]);
|
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]);
|
||||||
logger.info(`${logModule} package ${name} installed to ${extensionPackagesRoot()}`);
|
logger.info(`${logModule} package ${name} installed to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||||
} finally {
|
} finally {
|
||||||
this.installLock.release();
|
this.installLock.release();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private npm(args: string[]): Promise<void> {
|
private npm(args: string[]): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = child_process.fork(this.npmPath, args, {
|
const child = child_process.fork(this.npmPath, args, {
|
||||||
cwd: extensionPackagesRoot(),
|
cwd: this.dependencies.extensionPackageRootDirectory,
|
||||||
silent: true,
|
silent: true,
|
||||||
env: {},
|
env: {},
|
||||||
});
|
});
|
||||||
@ -108,5 +108,3 @@ export class ExtensionInstaller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionInstaller = new ExtensionInstaller();
|
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
|
const extensionPackageRootDirectoryInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(directoryForUserDataInjectable),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionPackageRootDirectoryInjectable;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import extensionInstallerInjectable from "../extension-installer.injectable";
|
||||||
|
|
||||||
|
const installExtensionInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(extensionInstallerInjectable).installPackage,
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installExtensionInjectable;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import extensionInstallerInjectable from "../extension-installer.injectable";
|
||||||
|
|
||||||
|
const installExtensionsInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(extensionInstallerInjectable).installPackages,
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default installExtensionsInjectable;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { createExtensionInstance } from "./create-extension-instance";
|
||||||
|
import fileSystemProvisionerStoreInjectable from "./file-system-provisioner-store/file-system-provisioner-store.injectable";
|
||||||
|
|
||||||
|
const createExtensionInstanceInjectable = getInjectable({
|
||||||
|
instantiate: (di) => createExtensionInstance({
|
||||||
|
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createExtensionInstanceInjectable;
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { LensExtensionConstructor } from "../../lens-extension";
|
||||||
|
import type { InstalledExtension } from "../../extension-discovery/extension-discovery";
|
||||||
|
import {
|
||||||
|
LensExtensionDependencies,
|
||||||
|
setLensExtensionDependencies,
|
||||||
|
} from "../../lens-extension-set-dependencies";
|
||||||
|
|
||||||
|
export const createExtensionInstance =
|
||||||
|
(dependencies: LensExtensionDependencies) =>
|
||||||
|
(ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => {
|
||||||
|
const instance = new ExtensionClass(extension);
|
||||||
|
|
||||||
|
instance[setLensExtensionDependencies](dependencies);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import path from "path";
|
||||||
|
import directoryForUserDataInjectable from "../../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
|
const directoryForExtensionDataInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
path.join(di.inject(directoryForUserDataInjectable), "extension_data"),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForExtensionDataInjectable;
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
||||||
|
import directoryForExtensionDataInjectable from "./directory-for-extension-data/directory-for-extension-data.injectable";
|
||||||
|
|
||||||
|
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
FileSystemProvisionerStore.createInstance({
|
||||||
|
directoryForExtensionData: di.inject(
|
||||||
|
directoryForExtensionDataInjectable,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default fileSystemProvisionerStoreInjectable;
|
||||||
@ -24,24 +24,28 @@ import { SHA256 } from "crypto-js";
|
|||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import { action, makeObservable, observable } from "mobx";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { BaseStore } from "../common/base-store";
|
import { BaseStore } from "../../../../common/base-store";
|
||||||
import type { LensExtensionId } from "../extensions/lens-extension";
|
import type { LensExtensionId } from "../../../lens-extension";
|
||||||
import { toJS } from "../common/utils";
|
import { toJS } from "../../../../common/utils";
|
||||||
import { AppPaths } from "../common/app-paths";
|
|
||||||
|
|
||||||
interface FSProvisionModel {
|
interface FSProvisionModel {
|
||||||
extensions: Record<string, string>; // extension names to paths
|
extensions: Record<string, string>; // extension names to paths
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
interface Dependencies {
|
||||||
|
directoryForExtensionData: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||||
readonly displayName = "FilesystemProvisionerStore";
|
readonly displayName = "FilesystemProvisionerStore";
|
||||||
registeredExtensions = observable.map<LensExtensionId, string>();
|
registeredExtensions = observable.map<LensExtensionId, string>();
|
||||||
|
|
||||||
constructor() {
|
constructor(private dependencies: Dependencies) {
|
||||||
super({
|
super({
|
||||||
configName: "lens-filesystem-provisioner-store",
|
configName: "lens-filesystem-provisioner-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
});
|
});
|
||||||
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
@ -56,7 +60,8 @@ export class FilesystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
|||||||
if (!this.registeredExtensions.has(extensionName)) {
|
if (!this.registeredExtensions.has(extensionName)) {
|
||||||
const salt = randomBytes(32).toString("hex");
|
const salt = randomBytes(32).toString("hex");
|
||||||
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
||||||
const dirPath = path.resolve(AppPaths.get("userData"), "extension_data", hashedName);
|
|
||||||
|
const dirPath = path.resolve(this.dependencies.directoryForExtensionData, hashedName);
|
||||||
|
|
||||||
this.registeredExtensions.set(extensionName, dirPath);
|
this.registeredExtensions.set(extensionName, dirPath);
|
||||||
}
|
}
|
||||||
@ -20,9 +20,17 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { ExtensionLoader } from "./extension-loader";
|
import { ExtensionLoader } from "./extension-loader";
|
||||||
|
import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable";
|
||||||
|
import createExtensionInstanceInjectable
|
||||||
|
from "./create-extension-instance/create-extension-instance.injectable";
|
||||||
|
|
||||||
const extensionLoaderInjectable = getInjectable({
|
const extensionLoaderInjectable = getInjectable({
|
||||||
instantiate: () => new ExtensionLoader(),
|
instantiate: (di) =>
|
||||||
|
new ExtensionLoader({
|
||||||
|
updateExtensionsState: di.inject(updateExtensionsStateInjectable),
|
||||||
|
createExtensionInstance: di.inject(createExtensionInstanceInjectable),
|
||||||
|
}),
|
||||||
|
|
||||||
lifecycle: lifecycleEnum.singleton,
|
lifecycle: lifecycleEnum.singleton,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -24,23 +24,28 @@ import { EventEmitter } from "events";
|
|||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { AppPaths } from "../../common/app-paths";
|
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../../common/ipc";
|
import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../../common/ipc";
|
||||||
import { Disposer, toJS } from "../../common/utils";
|
import { Disposer, toJS } from "../../common/utils";
|
||||||
import logger from "../../main/logger";
|
import logger from "../../main/logger";
|
||||||
import type { KubernetesCluster } from "../common-api/catalog";
|
import type { KubernetesCluster } from "../common-api/catalog";
|
||||||
import type { InstalledExtension } from "../extension-discovery";
|
import type { InstalledExtension } from "../extension-discovery/extension-discovery";
|
||||||
import { ExtensionsStore } from "../extensions-store";
|
|
||||||
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
|
||||||
import type { LensRendererExtension } from "../lens-renderer-extension";
|
import type { LensRendererExtension } from "../lens-renderer-extension";
|
||||||
import * as registries from "../registries";
|
import * as registries from "../registries";
|
||||||
|
import type { LensExtensionState } from "../extensions-store/extensions-store";
|
||||||
export function extensionPackagesRoot() {
|
|
||||||
return path.join(AppPaths.get("userData"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const logModule = "[EXTENSIONS-LOADER]";
|
const logModule = "[EXTENSIONS-LOADER]";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
updateExtensionsState: (extensionsState: Record<LensExtensionId, LensExtensionState>) => void
|
||||||
|
createExtensionInstance: (ExtensionClass: LensExtensionConstructor, extension: InstalledExtension) => LensExtension,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtensionLoading {
|
||||||
|
isBundled: boolean,
|
||||||
|
loaded: Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads installed extensions to the Lens application
|
* Loads installed extensions to the Lens application
|
||||||
*/
|
*/
|
||||||
@ -75,8 +80,9 @@ export class ExtensionLoader {
|
|||||||
return when(() => this.isLoaded);
|
return when(() => this.isLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor(protected dependencies : Dependencies) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
observe(this.instances, change => {
|
observe(this.instances, change => {
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case "add":
|
case "add":
|
||||||
@ -154,10 +160,13 @@ export class ExtensionLoader {
|
|||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// save state on change `extension.isEnabled`
|
reaction(
|
||||||
reaction(() => this.storeState, extensionsState => {
|
() => this.storeState,
|
||||||
ExtensionsStore.getInstance().mergeState(extensionsState);
|
|
||||||
});
|
(state) => {
|
||||||
|
this.dependencies.updateExtensionsState(state);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
|
initExtensions(extensions?: Map<LensExtensionId, InstalledExtension>) {
|
||||||
@ -253,7 +262,7 @@ export class ExtensionLoader {
|
|||||||
this.autoInitExtensions(() => Promise.resolve([]));
|
this.autoInitExtensions(() => Promise.resolve([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer = () => {
|
||||||
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
logger.debug(`${logModule}: load on main renderer (cluster manager)`);
|
||||||
|
|
||||||
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
return this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
@ -275,9 +284,9 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
return removeItems;
|
return removeItems;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
loadOnClusterRenderer(entity: KubernetesCluster) {
|
loadOnClusterRenderer = (entity: KubernetesCluster) => {
|
||||||
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
|
||||||
|
|
||||||
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
this.autoInitExtensions(async (extension: LensRendererExtension) => {
|
||||||
@ -304,12 +313,12 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
return removeItems;
|
return removeItems;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
|
||||||
const loadingExtensions: { isBundled: boolean, loaded: Promise<void> }[] = [];
|
const loadingExtensions: ExtensionLoading[] = [];
|
||||||
|
|
||||||
reaction(() => this.toJSON(), installedExtensions => {
|
reaction(() => this.toJSON(), async installedExtensions => {
|
||||||
for (const [extId, extension] of installedExtensions) {
|
for (const [extId, extension] of installedExtensions) {
|
||||||
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name);
|
||||||
|
|
||||||
@ -322,7 +331,10 @@ export class ExtensionLoader {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = new LensExtensionClass(extension);
|
const instance = this.dependencies.createExtensionInstance(
|
||||||
|
LensExtensionClass,
|
||||||
|
extension,
|
||||||
|
);
|
||||||
|
|
||||||
const loaded = instance.enable(register).catch((err) => {
|
const loaded = instance.enable(register).catch((err) => {
|
||||||
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
logger.error(`${logModule}: failed to enable`, { ext: extension, err });
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
|
||||||
|
|
||||||
|
const updateExtensionsStateInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default updateExtensionsStateInjectable;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import directoryForUserDataInjectable
|
||||||
|
from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
|
||||||
|
const extensionPackagesRootInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(directoryForUserDataInjectable),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionPackagesRootInjectable;
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ExtensionsStore } from "./extensions-store";
|
||||||
|
|
||||||
|
const extensionsStoreInjectable = getInjectable({
|
||||||
|
instantiate: () => ExtensionsStore.createInstance(),
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default extensionsStoreInjectable;
|
||||||
@ -19,10 +19,10 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { LensExtensionId } from "./lens-extension";
|
import type { LensExtensionId } from "../lens-extension";
|
||||||
import { BaseStore } from "../common/base-store";
|
import { action, computed, makeObservable, observable } from "mobx";
|
||||||
import { action, computed, observable, makeObservable } from "mobx";
|
import { toJS } from "../../common/utils";
|
||||||
import { toJS } from "../common/utils";
|
import { BaseStore } from "../../common/base-store";
|
||||||
|
|
||||||
export interface LensExtensionsStoreModel {
|
export interface LensExtensionsStoreModel {
|
||||||
extensions: Record<LensExtensionId, LensExtensionState>;
|
extensions: Record<LensExtensionId, LensExtensionState>;
|
||||||
@ -59,9 +59,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
mergeState(extensionsState: Record<LensExtensionId, LensExtensionState>) {
|
mergeState = (extensionsState: Record<LensExtensionId, LensExtensionState>) => {
|
||||||
this.state.merge(extensionsState);
|
this.state.merge(extensionsState);
|
||||||
}
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
protected fromStore({ extensions }: LensExtensionsStoreModel) {
|
||||||
30
src/extensions/lens-extension-set-dependencies.ts
Normal file
30
src/extensions/lens-extension-set-dependencies.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { FileSystemProvisionerStore } from "./extension-loader/create-extension-instance/file-system-provisioner-store/file-system-provisioner-store";
|
||||||
|
|
||||||
|
// This symbol encapsulates setting of dependencies to only happen locally in Lens Core
|
||||||
|
// and not by e.g. authors of extensions
|
||||||
|
export const setLensExtensionDependencies = Symbol("set-lens-extension-dependencies");
|
||||||
|
|
||||||
|
export interface LensExtensionDependencies {
|
||||||
|
fileSystemProvisionerStore: FileSystemProvisionerStore
|
||||||
|
}
|
||||||
@ -19,13 +19,16 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { InstalledExtension } from "./extension-discovery";
|
import type { InstalledExtension } from "./extension-discovery/extension-discovery";
|
||||||
import { action, observable, makeObservable, computed } from "mobx";
|
import { action, observable, makeObservable, computed } from "mobx";
|
||||||
import { FilesystemProvisionerStore } from "../main/extension-filesystem";
|
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import type { ProtocolHandlerRegistration } from "./registries";
|
import type { ProtocolHandlerRegistration } from "./registries";
|
||||||
import type { PackageJson } from "type-fest";
|
import type { PackageJson } from "type-fest";
|
||||||
import { Disposer, disposer } from "../common/utils";
|
import { Disposer, disposer } from "../common/utils";
|
||||||
|
import {
|
||||||
|
LensExtensionDependencies,
|
||||||
|
setLensExtensionDependencies,
|
||||||
|
} from "./lens-extension-set-dependencies";
|
||||||
|
|
||||||
export type LensExtensionId = string; // path to manifest (package.json)
|
export type LensExtensionId = string; // path to manifest (package.json)
|
||||||
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
|
||||||
@ -75,6 +78,12 @@ export class LensExtension {
|
|||||||
return this.manifest.description;
|
return this.manifest.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dependencies: LensExtensionDependencies;
|
||||||
|
|
||||||
|
[setLensExtensionDependencies] = (dependencies: LensExtensionDependencies) => {
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getExtensionFileFolder returns the path to an already created folder. This
|
* getExtensionFileFolder returns the path to an already created folder. This
|
||||||
* folder is for the sole use of this extension.
|
* folder is for the sole use of this extension.
|
||||||
@ -83,7 +92,7 @@ export class LensExtension {
|
|||||||
* folder name.
|
* folder name.
|
||||||
*/
|
*/
|
||||||
async getExtensionFileFolder(): Promise<string> {
|
async getExtensionFileFolder(): Promise<string> {
|
||||||
return FilesystemProvisionerStore.getInstance().requestDirectory(this.id);
|
return this.dependencies.fileSystemProvisionerStore.requestDirectory(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|||||||
@ -26,10 +26,10 @@ import React from "react";
|
|||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stderr, stdout } from "process";
|
import { stderr, stdout } from "process";
|
||||||
import { TerminalStore } from "../../../renderer/components/dock/terminal.store";
|
|
||||||
import { ThemeStore } from "../../../renderer/theme.store";
|
import { ThemeStore } from "../../../renderer/theme.store";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { AppPaths } from "../../../common/app-paths";
|
import { getDisForUnitTesting } from "../../../test-utils/get-dis-for-unit-testing";
|
||||||
|
import mockFs from "mock-fs";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -47,14 +47,18 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
let ext: LensExtension = null;
|
let ext: LensExtension = null;
|
||||||
|
|
||||||
describe("page registry tests", () => {
|
describe("page registry tests", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const dis = getDisForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
mockFs();
|
||||||
|
|
||||||
|
await dis.runSetups();
|
||||||
|
|
||||||
ext = new LensExtension({
|
ext = new LensExtension({
|
||||||
manifest: {
|
manifest: {
|
||||||
name: "foo-bar",
|
name: "foo-bar",
|
||||||
@ -69,7 +73,6 @@ describe("page registry tests", () => {
|
|||||||
});
|
});
|
||||||
UserStore.createInstance();
|
UserStore.createInstance();
|
||||||
ThemeStore.createInstance();
|
ThemeStore.createInstance();
|
||||||
TerminalStore.createInstance();
|
|
||||||
ClusterPageRegistry.createInstance();
|
ClusterPageRegistry.createInstance();
|
||||||
GlobalPageRegistry.createInstance().add({
|
GlobalPageRegistry.createInstance().add({
|
||||||
id: "page-with-params",
|
id: "page-with-params",
|
||||||
@ -105,10 +108,10 @@ describe("page registry tests", () => {
|
|||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
GlobalPageRegistry.resetInstance();
|
GlobalPageRegistry.resetInstance();
|
||||||
ClusterPageRegistry.resetInstance();
|
ClusterPageRegistry.resetInstance();
|
||||||
TerminalStore.resetInstance();
|
|
||||||
ThemeStore.resetInstance();
|
ThemeStore.resetInstance();
|
||||||
UserStore.resetInstance();
|
UserStore.resetInstance();
|
||||||
fse.remove("tmp");
|
fse.remove("tmp");
|
||||||
|
mockFs.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getPageUrl", () => {
|
describe("getPageUrl", () => {
|
||||||
|
|||||||
@ -18,9 +18,15 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||||
|
import createTerminalTabInjectable from "../../renderer/components/dock/create-terminal-tab/create-terminal-tab.injectable";
|
||||||
|
import terminalStoreInjectable from "../../renderer/components/dock/terminal-store/terminal-store.injectable";
|
||||||
|
import { asLegacyGlobalObjectForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||||
|
import logTabStoreInjectable from "../../renderer/components/dock/log-tab-store/log-tab-store.injectable";
|
||||||
|
import { asLegacyGlobalSingletonForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-singleton-for-extension-api";
|
||||||
|
import { TerminalStore as TerminalStoreClass } from "../../renderer/components/dock/terminal-store/terminal.store";
|
||||||
|
|
||||||
import commandOverlayInjectable from "../../renderer/components/command-palette/command-overlay.injectable";
|
import commandOverlayInjectable from "../../renderer/components/command-palette/command-overlay.injectable";
|
||||||
import { asLegacyGlobalObjectForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
export * from "../../renderer/components/layout/main-layout";
|
export * from "../../renderer/components/layout/main-layout";
|
||||||
@ -74,5 +80,9 @@ export * from "../../renderer/components/+events/kube-event-details";
|
|||||||
|
|
||||||
// specific exports
|
// specific exports
|
||||||
export * from "../../renderer/components/status-brick";
|
export * from "../../renderer/components/status-brick";
|
||||||
export { terminalStore, createTerminalTab, TerminalStore } from "../../renderer/components/dock/terminal.store";
|
|
||||||
export { logTabStore } from "../../renderer/components/dock/log-tab.store";
|
export const createTerminalTab = asLegacyGlobalFunctionForExtensionApi(createTerminalTabInjectable);
|
||||||
|
export const TerminalStore = asLegacyGlobalSingletonForExtensionApi(TerminalStoreClass, terminalStoreInjectable);
|
||||||
|
export const terminalStore = asLegacyGlobalObjectForExtensionApi(terminalStoreInjectable);
|
||||||
|
export const logTabStore = asLegacyGlobalObjectForExtensionApi(logTabStoreInjectable);
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export type { NetworkPolicyStore } from "../../renderer/components/+network-poli
|
|||||||
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store";
|
export type { PersistentVolumesStore } from "../../renderer/components/+storage-volumes/volumes.store";
|
||||||
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store";
|
export type { VolumeClaimStore } from "../../renderer/components/+storage-volume-claims/volume-claim.store";
|
||||||
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store";
|
export type { StorageClassStore } from "../../renderer/components/+storage-classes/storage-class.store";
|
||||||
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace.store";
|
export type { NamespaceStore } from "../../renderer/components/+namespaces/namespace-store/namespace.store";
|
||||||
export type { ServiceAccountsStore } from "../../renderer/components/+user-management/+service-accounts/store";
|
export type { ServiceAccountsStore } from "../../renderer/components/+user-management/+service-accounts/store";
|
||||||
export type { RolesStore } from "../../renderer/components/+user-management/+roles/store";
|
export type { RolesStore } from "../../renderer/components/+user-management/+roles/store";
|
||||||
export type { RoleBindingsStore } from "../../renderer/components/+user-management/+role-bindings/store";
|
export type { RoleBindingsStore } from "../../renderer/components/+user-management/+role-bindings/store";
|
||||||
|
|||||||
@ -45,25 +45,29 @@ jest.mock("winston", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../common/ipc");
|
jest.mock("../../common/ipc");
|
||||||
jest.mock("../context-handler");
|
|
||||||
jest.mock("request");
|
jest.mock("request");
|
||||||
jest.mock("request-promise-native");
|
jest.mock("request-promise-native");
|
||||||
|
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { Cluster } from "../cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import { Kubectl } from "../kubectl";
|
import { Kubectl } from "../kubectl/kubectl";
|
||||||
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import type { ClusterModel } from "../../common/cluster-types";
|
||||||
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
describe("create clusters", () => {
|
describe("create clusters", () => {
|
||||||
beforeEach(() => {
|
let cluster: Cluster;
|
||||||
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
|
||||||
|
|
||||||
let c: Cluster;
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"minikube-config.yml": JSON.stringify({
|
"minikube-config.yml": JSON.stringify({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
@ -89,8 +93,14 @@ describe("create clusters", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
|
createCluster = di.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true));
|
||||||
c = new Cluster({
|
|
||||||
|
cluster = createCluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
@ -102,48 +112,39 @@ describe("create clusters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
|
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
|
||||||
expect(c.apiUrl).toBe("https://192.168.64.3:8443");
|
expect(cluster.apiUrl).toBe("https://192.168.64.3:8443");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reconnect should not throw if contextHandler is missing", () => {
|
it("reconnect should not throw if contextHandler is missing", () => {
|
||||||
expect(() => c.reconnect()).not.toThrowError();
|
expect(() => cluster.reconnect()).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("disconnect should not throw if contextHandler is missing", () => {
|
it("disconnect should not throw if contextHandler is missing", () => {
|
||||||
expect(() => c.disconnect()).not.toThrowError();
|
expect(() => cluster.disconnect()).not.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("activating cluster should try to connect to cluster and do a refresh", async () => {
|
it("activating cluster should try to connect to cluster and do a refresh", async () => {
|
||||||
|
const cluster = createCluster({
|
||||||
const c = new class extends Cluster {
|
|
||||||
// only way to mock protected methods, without these we leak promises
|
|
||||||
protected bindEvents() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
async ensureKubectl() {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
}({
|
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
});
|
});
|
||||||
|
|
||||||
c.contextHandler = {
|
cluster.contextHandler = {
|
||||||
ensureServer: jest.fn(),
|
ensureServer: jest.fn(),
|
||||||
stopServer: jest.fn(),
|
stopServer: jest.fn(),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
jest.spyOn(c, "reconnect");
|
jest.spyOn(cluster, "reconnect");
|
||||||
jest.spyOn(c, "canI");
|
jest.spyOn(cluster, "canI");
|
||||||
jest.spyOn(c, "refreshConnectionStatus");
|
jest.spyOn(cluster, "refreshConnectionStatus");
|
||||||
|
|
||||||
await c.activate();
|
await cluster.activate();
|
||||||
|
|
||||||
expect(c.reconnect).toBeCalled();
|
expect(cluster.reconnect).toBeCalled();
|
||||||
expect(c.refreshConnectionStatus).toBeCalled();
|
expect(cluster.refreshConnectionStatus).toBeCalled();
|
||||||
|
|
||||||
c.disconnect();
|
cluster.disconnect();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,10 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserStore } from "../../common/user-store";
|
import { UserStore } from "../../common/user-store";
|
||||||
import { ContextHandler } from "../context-handler";
|
import type { ContextHandler } from "../context-handler/context-handler";
|
||||||
import { PrometheusProvider, PrometheusProviderRegistry, PrometheusService } from "../prometheus";
|
import { PrometheusProvider, PrometheusProviderRegistry, PrometheusService } from "../prometheus";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { AppPaths } from "../../common/app-paths";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
app: {
|
app: {
|
||||||
@ -77,25 +79,28 @@ class TestProvider extends PrometheusProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHandler() {
|
const clusterStub = {
|
||||||
return new ContextHandler(({
|
|
||||||
getProxyKubeconfig: (): any => ({
|
getProxyKubeconfig: (): any => ({
|
||||||
makeApiClient: (): any => undefined,
|
makeApiClient: (): any => undefined,
|
||||||
}),
|
}),
|
||||||
apiUrl: "http://localhost:81",
|
apiUrl: "http://localhost:81",
|
||||||
}) as any);
|
} as Cluster;
|
||||||
}
|
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
describe("ContextHandler", () => {
|
describe("ContextHandler", () => {
|
||||||
beforeEach(() => {
|
let createContextHandler: (cluster: Cluster) => ContextHandler;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"tmp": {},
|
"tmp": {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
|
createContextHandler = di.inject(createContextHandlerInjectable);
|
||||||
|
|
||||||
PrometheusProviderRegistry.createInstance();
|
PrometheusProviderRegistry.createInstance();
|
||||||
UserStore.createInstance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -124,7 +129,12 @@ describe("ContextHandler", () => {
|
|||||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => (getHandler() as any).getPrometheusService()).rejects.toBeDefined();
|
expect(() => {
|
||||||
|
// TODO: Unit test shouldn't access protected or private methods
|
||||||
|
const contextHandler = createContextHandler(clusterStub) as any;
|
||||||
|
|
||||||
|
return contextHandler.getPrometheusService();
|
||||||
|
}).rejects.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -150,7 +160,10 @@ describe("ContextHandler", () => {
|
|||||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = await (getHandler() as any).getPrometheusService();
|
// TODO: Unit test shouldn't access protected or private methods
|
||||||
|
const contextHandler = createContextHandler(clusterStub) as any;
|
||||||
|
|
||||||
|
const service = await contextHandler.getPrometheusService();
|
||||||
|
|
||||||
expect(service.id === `id_${failures}`);
|
expect(service.id === `id_${failures}`);
|
||||||
});
|
});
|
||||||
@ -178,7 +191,10 @@ describe("ContextHandler", () => {
|
|||||||
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
reg.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = await (getHandler() as any).getPrometheusService();
|
// TODO: Unit test shouldn't access protected or private methods
|
||||||
|
const contextHandler = createContextHandler(clusterStub) as any;
|
||||||
|
|
||||||
|
const service = await contextHandler.getPrometheusService();
|
||||||
|
|
||||||
expect(service.id === "id_0");
|
expect(service.id === "id_0");
|
||||||
});
|
});
|
||||||
@ -212,7 +228,10 @@ describe("ContextHandler", () => {
|
|||||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||||
}
|
}
|
||||||
|
|
||||||
const service = await (getHandler() as any).getPrometheusService();
|
// TODO: Unit test shouldn't access protected or private methods
|
||||||
|
const contextHandler = createContextHandler(clusterStub) as any;
|
||||||
|
|
||||||
|
const service = await contextHandler.getPrometheusService();
|
||||||
|
|
||||||
expect(service.id === "id_0");
|
expect(service.id === "id_0");
|
||||||
});
|
});
|
||||||
@ -225,7 +244,10 @@ describe("ContextHandler", () => {
|
|||||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||||
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
reg.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
||||||
|
|
||||||
const service = await (getHandler() as any).getPrometheusService();
|
// TODO: Unit test shouldn't access protected or private methods
|
||||||
|
const contextHandler = createContextHandler(clusterStub) as any;
|
||||||
|
|
||||||
|
const service = await contextHandler.getPrometheusService();
|
||||||
|
|
||||||
expect(service.id).not.toBe("id_2");
|
expect(service.id).not.toBe("id_2");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,8 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { ClusterModel } from "../../common/cluster-types";
|
||||||
|
|
||||||
jest.mock("winston", () => ({
|
jest.mock("winston", () => ({
|
||||||
format: {
|
format: {
|
||||||
colorize: jest.fn(),
|
colorize: jest.fn(),
|
||||||
@ -48,11 +50,11 @@ jest.mock("../../common/ipc");
|
|||||||
jest.mock("child_process");
|
jest.mock("child_process");
|
||||||
jest.mock("tcp-port-used");
|
jest.mock("tcp-port-used");
|
||||||
|
|
||||||
import { Cluster } from "../cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import { KubeAuthProxy } from "../kube-auth-proxy";
|
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
||||||
import { broadcastMessage } from "../../common/ipc";
|
import { broadcastMessage } from "../../common/ipc";
|
||||||
import { ChildProcess, spawn } from "child_process";
|
import { ChildProcess, spawn } from "child_process";
|
||||||
import { bundledKubectlPath, Kubectl } from "../kubectl";
|
import { bundledKubectlPath, Kubectl } from "../kubectl/kubectl";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { waitUntilUsed } from "tcp-port-used";
|
import { waitUntilUsed } from "tcp-port-used";
|
||||||
import { EventEmitter, Readable } from "stream";
|
import { EventEmitter, Readable } from "stream";
|
||||||
@ -60,7 +62,9 @@ import { UserStore } from "../../common/user-store";
|
|||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import { stdout, stderr } from "process";
|
import { stdout, stderr } from "process";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { AppPaths } from "../../common/app-paths";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
|
|
||||||
console = new Console(stdout, stderr);
|
console = new Console(stdout, stderr);
|
||||||
|
|
||||||
@ -68,25 +72,11 @@ const mockBroadcastIpc = broadcastMessage as jest.MockedFunction<typeof broadcas
|
|||||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||||
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
|
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>;
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
|
||||||
app: {
|
|
||||||
getVersion: () => "99.99.99",
|
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
handle: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
describe("kube auth proxy tests", () => {
|
describe("kube auth proxy tests", () => {
|
||||||
beforeEach(() => {
|
let createCluster: (model: ClusterModel) => Cluster;
|
||||||
|
let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const mockMinikubeConfig = {
|
const mockMinikubeConfig = {
|
||||||
@ -115,7 +105,16 @@ describe("kube auth proxy tests", () => {
|
|||||||
"tmp": {},
|
"tmp": {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs(mockMinikubeConfig);
|
mockFs(mockMinikubeConfig);
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
|
createCluster = di.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
|
createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);
|
||||||
|
|
||||||
UserStore.createInstance();
|
UserStore.createInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,7 +124,13 @@ describe("kube auth proxy tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("calling exit multiple times shouldn't throw", async () => {
|
it("calling exit multiple times shouldn't throw", async () => {
|
||||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "minikube-config.yml", contextName: "minikube" }), {});
|
const cluster = createCluster({
|
||||||
|
id: "foobar",
|
||||||
|
kubeConfigPath: "minikube-config.yml",
|
||||||
|
contextName: "minikube",
|
||||||
|
});
|
||||||
|
|
||||||
|
const kap = createKubeAuthProxy(cluster, {});
|
||||||
|
|
||||||
kap.exit();
|
kap.exit();
|
||||||
kap.exit();
|
kap.exit();
|
||||||
@ -211,9 +216,13 @@ describe("kube auth proxy tests", () => {
|
|||||||
});
|
});
|
||||||
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve());
|
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve());
|
||||||
|
|
||||||
const cluster = new Cluster({ id: "foobar", kubeConfigPath: "minikube-config.yml", contextName: "minikube" });
|
const cluster = createCluster({
|
||||||
|
id: "foobar",
|
||||||
|
kubeConfigPath: "minikube-config.yml",
|
||||||
|
contextName: "minikube",
|
||||||
|
});
|
||||||
|
|
||||||
proxy = new KubeAuthProxy(cluster, {});
|
proxy = createKubeAuthProxy(cluster, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call spawn and broadcast errors", async () => {
|
it("should call spawn and broadcast errors", async () => {
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const logger = {
|
const logger = {
|
||||||
silly: jest.fn(),
|
silly: jest.fn(),
|
||||||
debug: jest.fn(),
|
debug: jest.fn(),
|
||||||
@ -46,41 +45,29 @@ jest.mock("winston", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { KubeconfigManager } from "../kubeconfig-manager";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
|
||||||
import mockFs from "mock-fs";
|
import mockFs from "mock-fs";
|
||||||
import { Cluster } from "../cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import type { ContextHandler } from "../context-handler";
|
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import { loadYaml } from "@kubernetes/client-node";
|
import { loadYaml } from "@kubernetes/client-node";
|
||||||
import { Console } from "console";
|
import { Console } from "console";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { AppPaths } from "../../common/app-paths";
|
import createKubeconfigManagerInjectable from "../kubeconfig-manager/create-kubeconfig-manager.injectable";
|
||||||
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
jest.mock("electron", () => ({
|
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
app: {
|
|
||||||
getVersion: () => "99.99.99",
|
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
handle: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
console = new Console(process.stdout, process.stderr); // fix mockFS
|
console = new Console(process.stdout, process.stderr); // fix mockFS
|
||||||
|
|
||||||
describe("kubeconfig manager tests", () => {
|
describe("kubeconfig manager tests", () => {
|
||||||
let cluster: Cluster;
|
let cluster: Cluster;
|
||||||
let contextHandler: ContextHandler;
|
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
|
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockFs({
|
mockFs({
|
||||||
"minikube-config.yml": JSON.stringify({
|
"minikube-config.yml": JSON.stringify({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
@ -105,14 +92,22 @@ describe("kubeconfig manager tests", () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster = new Cluster({
|
await di.runSetups();
|
||||||
|
|
||||||
|
const createCluster = di.inject(createClusterInjectionToken);
|
||||||
|
|
||||||
|
createKubeconfigManager = di.inject(createKubeconfigManagerInjectable);
|
||||||
|
|
||||||
|
cluster = createCluster({
|
||||||
id: "foo",
|
id: "foo",
|
||||||
contextName: "minikube",
|
contextName: "minikube",
|
||||||
kubeConfigPath: "minikube-config.yml",
|
kubeConfigPath: "minikube-config.yml",
|
||||||
});
|
});
|
||||||
contextHandler = {
|
|
||||||
|
cluster.contextHandler = {
|
||||||
ensureServer: () => Promise.resolve(),
|
ensureServer: () => Promise.resolve(),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
jest.spyOn(KubeconfigManager.prototype, "resolveProxyUrl", "get").mockReturnValue("http://127.0.0.1:9191/foo");
|
jest.spyOn(KubeconfigManager.prototype, "resolveProxyUrl", "get").mockReturnValue("http://127.0.0.1:9191/foo");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,10 +116,10 @@ describe("kubeconfig manager tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should create 'temp' kube config with proxy", async () => {
|
it("should create 'temp' kube config with proxy", async () => {
|
||||||
const kubeConfManager = new KubeconfigManager(cluster, contextHandler);
|
const kubeConfManager = createKubeconfigManager(cluster);
|
||||||
|
|
||||||
expect(logger.error).not.toBeCalled();
|
expect(logger.error).not.toBeCalled();
|
||||||
expect(await kubeConfManager.getPath()).toBe(`tmp${path.sep}kubeconfig-foo`);
|
expect(await kubeConfManager.getPath()).toBe(`some-directory-for-temp${path.sep}kubeconfig-foo`);
|
||||||
// this causes an intermittent "ENXIO: no such device or address, read" error
|
// this causes an intermittent "ENXIO: no such device or address, read" error
|
||||||
// const file = await fse.readFile(await kubeConfManager.getPath());
|
// const file = await fse.readFile(await kubeConfManager.getPath());
|
||||||
const file = fse.readFileSync(await kubeConfManager.getPath());
|
const file = fse.readFileSync(await kubeConfManager.getPath());
|
||||||
@ -136,7 +131,8 @@ describe("kubeconfig manager tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
||||||
const kubeConfManager = new KubeconfigManager(cluster, contextHandler);
|
const kubeConfManager = createKubeconfigManager(cluster);
|
||||||
|
|
||||||
const configPath = await kubeConfManager.getPath();
|
const configPath = await kubeConfManager.getPath();
|
||||||
|
|
||||||
expect(await fse.pathExists(configPath)).toBe(true);
|
expect(await fse.pathExists(configPath)).toBe(true);
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AppPaths } from "../../common/app-paths";
|
|
||||||
import { Router } from "../router";
|
import { Router } from "../router";
|
||||||
|
|
||||||
jest.mock("electron", () => ({
|
jest.mock("electron", () => ({
|
||||||
@ -38,8 +37,6 @@ jest.mock("electron", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AppPaths.init();
|
|
||||||
|
|
||||||
describe("Router", () => {
|
describe("Router", () => {
|
||||||
it("blocks path traversal attacks", async () => {
|
it("blocks path traversal attacks", async () => {
|
||||||
const response: any = {
|
const response: any = {
|
||||||
|
|||||||
29
src/main/app-paths/app-name/app-name.injectable.ts
Normal file
29
src/main/app-paths/app-name/app-name.injectable.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import electronAppInjectable from "../get-electron-app-path/electron-app/electron-app.injectable";
|
||||||
|
|
||||||
|
const appNameInjectable = getInjectable({
|
||||||
|
instantiate: (di) => di.inject(electronAppInjectable).name,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appNameInjectable;
|
||||||
84
src/main/app-paths/app-paths.injectable.ts
Normal file
84
src/main/app-paths/app-paths.injectable.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
DependencyInjectionContainer,
|
||||||
|
getInjectable,
|
||||||
|
lifecycleEnum,
|
||||||
|
} from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
import {
|
||||||
|
appPathsInjectionToken,
|
||||||
|
appPathsIpcChannel,
|
||||||
|
} from "../../common/app-paths/app-path-injection-token";
|
||||||
|
|
||||||
|
import registerChannelInjectable from "./register-channel/register-channel.injectable";
|
||||||
|
import { getAppPaths } from "./get-app-paths";
|
||||||
|
import getElectronAppPathInjectable from "./get-electron-app-path/get-electron-app-path.injectable";
|
||||||
|
import setElectronAppPathInjectable from "./set-electron-app-path/set-electron-app-path.injectable";
|
||||||
|
import path from "path";
|
||||||
|
import appNameInjectable from "./app-name/app-name.injectable";
|
||||||
|
import directoryForIntegrationTestingInjectable from "./directory-for-integration-testing/directory-for-integration-testing.injectable";
|
||||||
|
|
||||||
|
const appPathsInjectable = getInjectable({
|
||||||
|
setup: (di) => {
|
||||||
|
const directoryForIntegrationTesting = di.inject(
|
||||||
|
directoryForIntegrationTestingInjectable,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (directoryForIntegrationTesting) {
|
||||||
|
setupPathForAppDataInIntegrationTesting(di, directoryForIntegrationTesting);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPathForUserData(di);
|
||||||
|
registerAppPathsChannel(di);
|
||||||
|
},
|
||||||
|
|
||||||
|
instantiate: (di) =>
|
||||||
|
getAppPaths({ getAppPath: di.inject(getElectronAppPathInjectable) }),
|
||||||
|
|
||||||
|
injectionToken: appPathsInjectionToken,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default appPathsInjectable;
|
||||||
|
|
||||||
|
const registerAppPathsChannel = (di: DependencyInjectionContainer) => {
|
||||||
|
const registerChannel = di.inject(registerChannelInjectable);
|
||||||
|
|
||||||
|
registerChannel(appPathsIpcChannel, () => di.inject(appPathsInjectable));
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupPathForUserData = (di: DependencyInjectionContainer) => {
|
||||||
|
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
|
||||||
|
const appName = di.inject(appNameInjectable);
|
||||||
|
const getAppPath = di.inject(getElectronAppPathInjectable);
|
||||||
|
|
||||||
|
const appDataPath = getAppPath("appData");
|
||||||
|
|
||||||
|
setElectronAppPath("userData", path.join(appDataPath, appName));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Todo: this kludge is here only until we have a proper place to setup integration testing.
|
||||||
|
const setupPathForAppDataInIntegrationTesting = (di: DependencyInjectionContainer, appDataPath: string) => {
|
||||||
|
const setElectronAppPath = di.inject(setElectronAppPathInjectable);
|
||||||
|
|
||||||
|
setElectronAppPath("appData", appDataPath);
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
const directoryForIntegrationTestingInjectable = getInjectable({
|
||||||
|
instantiate: () => process.env.CICD,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default directoryForIntegrationTestingInjectable;
|
||||||
30
src/main/app-paths/get-app-paths.ts
Normal file
30
src/main/app-paths/get-app-paths.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { fromPairs } from "lodash/fp";
|
||||||
|
import { pathNames, PathName } from "../../common/app-paths/app-path-names";
|
||||||
|
import type { AppPaths } from "../../common/app-paths/app-path-injection-token";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
getAppPath: (name: PathName) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAppPaths = ({ getAppPath }: Dependencies) =>
|
||||||
|
fromPairs(pathNames.map((name) => [name, getAppPath(name)])) as AppPaths;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
const electronAppInjectable = getInjectable({
|
||||||
|
instantiate: () => app,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default electronAppInjectable;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import electronAppInjectable from "./electron-app/electron-app.injectable";
|
||||||
|
import { getElectronAppPath } from "./get-electron-app-path";
|
||||||
|
|
||||||
|
const getElectronAppPathInjectable = getInjectable({
|
||||||
|
instantiate: (di) =>
|
||||||
|
getElectronAppPath({ app: di.inject(electronAppInjectable) }),
|
||||||
|
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getElectronAppPathInjectable;
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import electronAppInjectable from "./electron-app/electron-app.injectable";
|
||||||
|
import getElectronAppPathInjectable from "./get-electron-app-path.injectable";
|
||||||
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import type { App } from "electron";
|
||||||
|
import registerChannelInjectable from "../register-channel/register-channel.injectable";
|
||||||
|
|
||||||
|
describe("get-electron-app-path", () => {
|
||||||
|
let getElectronAppPath: (name: string) => string | null;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const di = getDiForUnitTesting({ doGeneralOverrides: false });
|
||||||
|
|
||||||
|
const appStub = {
|
||||||
|
name: "some-app-name",
|
||||||
|
|
||||||
|
getPath: (name: string) => {
|
||||||
|
if (name !== "some-existing-name") {
|
||||||
|
throw new Error("irrelevant");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "some-existing-app-path";
|
||||||
|
},
|
||||||
|
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
||||||
|
setPath: (_, __) => undefined,
|
||||||
|
} as App;
|
||||||
|
|
||||||
|
di.override(electronAppInjectable, () => appStub);
|
||||||
|
di.override(registerChannelInjectable, () => () => undefined);
|
||||||
|
|
||||||
|
await di.runSetups();
|
||||||
|
|
||||||
|
getElectronAppPath = di.inject(getElectronAppPathInjectable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given app path exists, when called, returns app path", () => {
|
||||||
|
const actual = getElectronAppPath("some-existing-name");
|
||||||
|
|
||||||
|
expect(actual).toBe("some-existing-app-path");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given app path does not exist, when called, returns null", () => {
|
||||||
|
const actual = getElectronAppPath("some-non-existing-name");
|
||||||
|
|
||||||
|
expect(actual).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import type { App } from "electron";
|
||||||
|
import type { PathName } from "../../../common/app-paths/app-path-names";
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
app: App;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getElectronAppPath =
|
||||||
|
({ app }: Dependencies) =>
|
||||||
|
(name: PathName) : string | null => {
|
||||||
|
try {
|
||||||
|
return app.getPath(name);
|
||||||
|
} catch (e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
|
||||||
|
const ipcMainInjectable = getInjectable({
|
||||||
|
instantiate: () => ipcMain,
|
||||||
|
lifecycle: lifecycleEnum.singleton,
|
||||||
|
causesSideEffects: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ipcMainInjectable;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user