Make base store non Singleton (#6690)

* Remove Singleton from BaseStore to remove global shared state

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove more usages of Singleton

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global execHelm with injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove last use of legacy global execHelm

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Extract BaseStore deps into constructor argument

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Introduce method to make store migrations injectable
- Use it for ClusterStore

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch HotbarStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch UserStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move migration utils into common/utils/

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Switch WeblinkStore to injectable migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in base-store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove tests that reference lastSeenVersion
- That value is not used anywhere in code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global .getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove usage of legacy global ClusterStore.getInstance

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add simple migrations dependency for stores without any preexisting migrations

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix messed up import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add typing to transient injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix typing in tests to satisfy requirement to have cacheFile

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* More consistent use of BaseStore.displayName

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add catching of error while starting main application

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing sentry to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unneeded appPathsInjectionToken
- Only had once impl, which was in common anyway

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add support for multiple "runAfter" runnables
- Needed so that several dependencies can be declared

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use multiple runAfter support to fix crash on renderer

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove traces

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix base store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix runManyFor tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for getConfigurationFileModel to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove overrides for configuration stores

- Now that there is an override for getConfiguration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Overhaul FS fakes with full in-memory filesystem
- This increases our confidence in fs related logic

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove use of global shared Electron.App

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add fake access support

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Handle copy as part of fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add ensureDir/Sync support to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Use pathExistsSync instead of fsInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add createReadStream to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add stat to fake FS

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test failures due to incomplete overrides

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully injectable-ize BaseStore so that ApplicationBuilder tests work

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Consolidate more bootstrapping into startFrame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move initializing CatalogCategories to runnable in bootstrap

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert contextMenuOpen initializers into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert navigateForExtension init to runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make cluster state sync fully injectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Move init hotbar store into runnables

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make LensTheme fully injectable and runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cleanup old code from missed from previous commit

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Manually split out terminal color names and fully type LensTheme

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix old imports

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unnecessart awaits

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fully cherry pick injectablizing custom monaco themes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix duplicate mock warning

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix incorrectly fully cherry picking new runnable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Complete cherry-pick of current cluster injcetablization

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix override file name

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix injecting before app paths are set up

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix ordering of runnable and order of injection

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all renderer runnables to late-inject style
- To help fix issues around injection time

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix react-beautiful-dnd mocks

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update and fix WriteJson(Sync) to fix error in tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix HotbarStore.load being called twice is being buggy

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update listing-active-helm-repositories-in-preferences snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar-and-tab-navigation-tests
- Move enabling extensions in tests to a proper location
- Fix flushing promises

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove props from dnd mock to make snapshot diffs smaller

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix import

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests by overriding things that are no longer overriden by default
- NOTE: They are overridden when using ApplicationBuilder

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix hotbar store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-loader tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix extension-discovery tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster-role-dialog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix user store tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix kubeconfig sync tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix sidebar and tab tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove unused code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix pick paths import type error and simplify signature

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error in legacy ipc registration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove another use of legacy requestOpenPathPicker

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of legacy global PathPicker.Pick

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix usage in light of changed prop names

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix catalog tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix more type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix test flakiness by removing side effects from userStore preferences

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix loading

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix type error

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix crash

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Cherry pick updated startFrameInjectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add tests to verify runMany behaviour in new possible incorrect configuration

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix init ordering during start frame

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix cluster state sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update snapshots after removing side-effects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add override for technical test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Correctly mark currentlyInClusterFrame as causedSideEffects

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Better formatting

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix behaviour regression

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add better logging

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix BaseStore sync

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Update last snapshot

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add global override for randomBytes

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Make startMainApplication not an injection time side effect

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Choose better names for start-frame runnable tokens

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove duplication of code in RunManyFor

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add unit tests and fix handling empty runAfter array

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Replace use of mobx from runManyFor with custom barrier

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Add missing test

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-12-15 17:07:19 +02:00
1 parent 7c5faaaf1d
commit 25f37ac1d1
364 files changed
+10035 -5246

No files matched your search

+8
View File
@@ -22,6 +22,10 @@ import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
import { apiResourceRecord, apiResources } from "../../common/rbac";
import listApiResourcesInjectable from "../../common/cluster/list-api-resources.injectable";
import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
console = new Console(process.stdout, process.stderr); // fix mockFS
@@ -56,6 +60,10 @@ describe("create clusters", () => {
setupPrometheus: jest.fn(),
ensureServer: jest.fn(),
} as ClusterContextHandler));
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
createCluster = di.inject(createClusterInjectionToken);
@@ -28,6 +28,10 @@ import normalizedPlatformInjectable from "../../common/vars/normalized-platform.
import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable";
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable";
console = new Console(stdout, stderr);
@@ -73,6 +77,10 @@ describe("kube auth proxy tests", () => {
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "some-directory-for-temp");
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
spawnMock = jest.fn();
di.override(spawnInjectable, () => spawnMock);
+10 -4
View File
@@ -25,8 +25,11 @@ import type { WriteFile } from "../../common/fs/write-file.injectable";
import writeFileInjectable from "../../common/fs/write-file.injectable";
import type { PathExists } from "../../common/fs/path-exists.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
import type { RemovePath } from "../../common/fs/remove.injectable";
import removePathInjectable from "../../common/fs/remove.injectable";
import pathExistsSyncInjectable from "../../common/fs/path-exists-sync.injectable";
import readJsonSyncInjectable from "../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../common/fs/write-json-sync.injectable";
const clusterServerUrl = "https://192.168.64.3:8443";
@@ -36,7 +39,7 @@ describe("kubeconfig manager tests", () => {
let di: DiContainer;
let loggerMock: jest.Mocked<Logger>;
let readFileMock: AsyncFnMock<ReadFile>;
let deleteFileMock: AsyncFnMock<DeleteFile>;
let deleteFileMock: AsyncFnMock<RemovePath>;
let writeFileMock: AsyncFnMock<WriteFile>;
let pathExistsMock: AsyncFnMock<PathExists>;
let kubeConfManager: KubeconfigManager;
@@ -50,6 +53,9 @@ describe("kubeconfig manager tests", () => {
di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
readFileMock = asyncFn();
di.override(readFileInjectable, () => readFileMock);
@@ -58,7 +64,7 @@ describe("kubeconfig manager tests", () => {
pathExistsMock = asyncFn();
di.override(pathExistsInjectable, () => pathExistsMock);
deleteFileMock = asyncFn();
di.override(deleteFileInjectable, () => deleteFileMock);
di.override(removePathInjectable, () => deleteFileMock);
loggerMock = {
warn: jest.fn(),
@@ -0,0 +1,14 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync";
const shouldBaseStoreDisableSyncInIpcListenerInjectable = getInjectable({
id: "should-base-store-disable-sync-in-ipc-listener",
instantiate: () => false,
injectionToken: shouldBaseStoreDisableSyncInIpcListenerInjectionToken,
});
export default shouldBaseStoreDisableSyncInIpcListenerInjectable;
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix";
const baseStoreIpcChannelPrefixInjectable = getInjectable({
id: "base-store-ipc-channel-prefix",
instantiate: () => ({
local: "store-sync-main",
remote: "store-sync-renderer",
}),
injectionToken: baseStoreIpcChannelPrefixesInjectionToken,
});
export default baseStoreIpcChannelPrefixInjectable;
@@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file";
import loggerInjectable from "../../common/logger.injectable";
const persistStateToConfigInjectable = getInjectable({
id: "persist-state-to-config",
instantiate: (di) => {
const logger = di.inject(loggerInjectable);
return (config, state) => {
logger.info(`[BASE-STORE]: saving ${config.path}...`);
for (const [key, value] of Object.entries(state)) {
config.set(key, value);
}
};
},
injectionToken: persistStateToConfigInjectionToken,
});
export default persistStateToConfigInjectable;
@@ -22,14 +22,18 @@ import kubeconfigSyncsInjectable from "../../../common/user-store/kubeconfig-syn
import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable";
import type { DiContainer } from "@ogre-tools/injectable";
import type { AsyncFnMock } from "@async-fn/jest";
import type { Stat } from "../../../common/fs/stat/stat.injectable";
import type { Stat } from "../../../common/fs/stat.injectable";
import asyncFn from "@async-fn/jest";
import statInjectable from "../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
import watchInjectable from "../../../common/fs/watch/watch.injectable";
import EventEmitter from "events";
import type { ReadStream, Stats } from "fs";
import createReadFileStreamInjectable from "../../../common/fs/create-read-file-stream.injectable";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
import pathExistsInjectable from "../../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
describe("kubeconfig-sync.source tests", () => {
let computeKubeconfigDiff: ComputeKubeconfigDiff;
@@ -43,6 +47,10 @@ describe("kubeconfig-sync.source tests", () => {
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-directory-for-temp");
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
clusters = new Map();
di.override(getClusterByIdInjectable, () => id => clusters.get(id));
@@ -10,7 +10,7 @@ import path from "path";
import { inspect } from "util";
import type { CatalogEntity } from "../../../common/catalog";
import type { Cluster } from "../../../common/cluster/cluster";
import statInjectable from "../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
import type { Watcher } from "../../../common/fs/watch/watch.injectable";
import watchInjectable from "../../../common/fs/watch/watch.injectable";
import type { Disposer } from "../../../common/utils";
@@ -4,7 +4,7 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { syncWeblinks } from "./weblinks";
import weblinkStoreInjectable from "../../common/weblink-store.injectable";
import weblinkStoreInjectable from "../../common/weblinks-store/weblink-store.injectable";
import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable";
const syncWeblinksInjectable = getInjectable({
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { computed, observable, reaction } from "mobx";
import type { WeblinkStore } from "../../common/weblink-store";
import type { WeblinkStore } from "../../common/weblinks-store/weblink-store";
import { WebLink } from "../../common/catalog-entities";
import type { CatalogEntityRegistry } from "../catalog";
import got from "got";
@@ -14,9 +14,9 @@ const startCatalogSyncInjectable = getInjectable({
return {
id: "start-catalog-sync",
run: async () => {
run: () => {
if (!catalogSyncToRenderer.started) {
await catalogSyncToRenderer.start();
catalogSyncToRenderer.start();
}
},
};
@@ -0,0 +1,97 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Move embedded kubeconfig into separate file and add reference to it to cluster settings
// convert file path cluster icons to their base64 encoded versions
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import type { ClusterModel } from "../../../common/cluster-types";
import readFileSyncInjectable from "../../../common/fs/read-file-sync.injectable";
import { loadConfigFromString } from "../../../common/kube-helpers";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
interface Pre360ClusterModel extends ClusterModel {
kubeConfig?: string;
}
import { getInjectable } from "@ogre-tools/injectable";
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
import readFileBufferSyncInjectable from "../../../common/fs/read-file-buffer-sync.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable";
const v360Beta1ClusterStoreMigrationInjectable = getInjectable({
id: "v3.6.0-beta.1-cluster-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigDirectoryInjectable);
const readFileSync = di.inject(readFileSyncInjectable);
const readFileBufferSync = di.inject(readFileBufferSyncInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const logger = di.inject(loggerInjectable);
const writeFileSync = di.inject(writeFileSyncInjectable);
return {
version: "3.6.0-beta.1",
run: (store) => {
const storedClusters = (store.get("clusters") ?? []) as Pre360ClusterModel[];
const migratedClusters: ClusterModel[] = [];
logger.info("Number of clusters to migrate: ", storedClusters.length);
for (const clusterModel of storedClusters) {
/**
* migrate kubeconfig
*/
try {
const absPath = getCustomKubeConfigDirectory(clusterModel.id);
if (!clusterModel.kubeConfig) {
continue;
}
// take the embedded kubeconfig and dump it into a file
writeFileSync(absPath, clusterModel.kubeConfig);
clusterModel.kubeConfigPath = absPath;
clusterModel.contextName = loadConfigFromString(readFileSync(absPath)).config.getCurrentContext();
delete clusterModel.kubeConfig;
} catch (error) {
logger.info(`Failed to migrate Kubeconfig for cluster "${clusterModel.id}", removing clusterModel...`, error);
continue;
}
/**
* migrate cluster icon
*/
try {
if (clusterModel.preferences?.icon) {
logger.info(`migrating ${clusterModel.preferences.icon} for ${clusterModel.preferences.clusterName}`);
const iconPath = clusterModel.preferences.icon.replace("store://", "");
const fileData = readFileBufferSync(joinPaths(userDataPath, iconPath));
clusterModel.preferences.icon = `data:;base64,${fileData.toString("base64")}`;
} else {
delete clusterModel.preferences?.icon;
}
} catch (error) {
logger.info(`Failed to migrate cluster icon for cluster "${clusterModel.id}"`, error);
delete clusterModel.preferences?.icon;
}
migratedClusters.push(clusterModel);
}
store.set("clusters", migratedClusters);
},
};
},
injectionToken: clusterStoreMigrationInjectionToken,
});
export default v360Beta1ClusterStoreMigrationInjectable;
@@ -0,0 +1,63 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
import type { ClusterModel } from "../../../common/cluster-types";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import { isErrnoException } from "../../../common/utils";
interface Pre500WorkspaceStoreModel {
workspaces: {
id: string;
name: string;
}[];
}
const v500Beta10ClusterStoreMigrationInjectable = getInjectable({
id: "v5.0.0-beta.10-cluster-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
return {
version: "5.0.0-beta.10",
run(store) {
try {
const workspaceData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
const workspaces = new Map<string, string>(); // mapping from WorkspaceId to name
for (const { id, name } of workspaceData.workspaces) {
workspaces.set(id, name);
}
const clusters = (store.get("clusters") ?? []) as ClusterModel[];
for (const cluster of clusters) {
if (cluster.workspace) {
const workspace = workspaces.get(cluster.workspace);
if (workspace) {
(cluster.labels ??= {}).workspace = workspace;
}
}
}
store.set("clusters", clusters);
} catch (error) {
if (isErrnoException(error) && !(error.code === "ENOENT" && error.path?.endsWith("lens-workspace-store.json"))) {
// ignore lens-workspace-store.json being missing
throw error;
}
}
},
};
},
injectionToken: clusterStoreMigrationInjectionToken,
});
export default v500Beta10ClusterStoreMigrationInjectable;
@@ -0,0 +1,135 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../../../common/cluster-types";
import { moveSync, removeSync } from "fs-extra";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { isDefined } from "../../../common/utils";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
function mergePrometheusPreferences(left: ClusterPrometheusPreferences, right: ClusterPrometheusPreferences): ClusterPrometheusPreferences {
if (left.prometheus && left.prometheusProvider) {
return {
prometheus: left.prometheus,
prometheusProvider: left.prometheusProvider,
};
}
if (right.prometheus && right.prometheusProvider) {
return {
prometheus: right.prometheus,
prometheusProvider: right.prometheusProvider,
};
}
return {};
}
function mergePreferences(left: ClusterPreferences, right: ClusterPreferences): ClusterPreferences {
return {
terminalCWD: left.terminalCWD || right.terminalCWD || undefined,
clusterName: left.clusterName || right.clusterName || undefined,
iconOrder: left.iconOrder || right.iconOrder || undefined,
icon: left.icon || right.icon || undefined,
httpsProxy: left.httpsProxy || right.httpsProxy || undefined,
hiddenMetrics: mergeSet(left.hiddenMetrics ?? [], right.hiddenMetrics ?? []),
...mergePrometheusPreferences(left, right),
};
}
function mergeLabels(left: Record<string, string>, right: Record<string, string>): Record<string, string> {
return {
...right,
...left,
};
}
function mergeSet(...iterables: Iterable<string | undefined>[]): string[] {
const res = new Set<string>();
for (const iterable of iterables) {
for (const val of iterable) {
if (val) {
res.add(val);
}
}
}
return [...res];
}
function mergeClusterModel(prev: ClusterModel, right: Omit<ClusterModel, "id">): ClusterModel {
return {
id: prev.id,
kubeConfigPath: prev.kubeConfigPath,
contextName: prev.contextName,
preferences: mergePreferences(prev.preferences ?? {}, right.preferences ?? {}),
metadata: prev.metadata,
labels: mergeLabels(prev.labels ?? {}, right.labels ?? {}),
accessibleNamespaces: mergeSet(prev.accessibleNamespaces ?? [], right.accessibleNamespaces ?? []),
workspace: prev.workspace || right.workspace,
workspaces: mergeSet([prev.workspace, right.workspace], prev.workspaces ?? [], right.workspaces ?? []),
};
}
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
import { generateNewIdFor } from "../../../common/utils/generate-new-id-for";
const v500Beta13ClusterStoreMigrationInjectable = getInjectable({
id: "v5.0.0-beta.13-cluster-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const logger = di.inject(loggerInjectable);
const moveStorageFolder = ({ folder, newId, oldId }: { folder: string; newId: string; oldId: string }): void => {
const oldPath = joinPaths(folder, `${oldId}.json`);
const newPath = joinPaths(folder, `${newId}.json`);
try {
moveSync(oldPath, newPath);
} catch (error) {
if (String(error).includes("dest already exists")) {
logger.info(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`);
removeSync(oldPath);
}
}
};
return {
version: "5.0.0-beta.13",
run(store) {
const folder = joinPaths(userDataPath, "lens-local-storage");
const oldClusters = (store.get("clusters") ?? []) as ClusterModel[];
const clusters = new Map<string, ClusterModel>();
for (const { id: oldId, ...cluster } of oldClusters) {
const newId = generateNewIdFor(cluster);
const newCluster = clusters.get(newId);
if (newCluster) {
logger.info(`Duplicate entries for ${newId}`, { oldId });
clusters.set(newId, mergeClusterModel(newCluster, cluster));
} else {
logger.info(`First entry for ${newId}`, { oldId });
clusters.set(newId, {
...cluster,
id: newId,
workspaces: [cluster.workspace].filter(isDefined),
});
moveStorageFolder({ folder, newId, oldId });
}
}
store.set("clusters", [...clusters.values()]);
},
};
},
injectionToken: clusterStoreMigrationInjectionToken,
});
export default v500Beta13ClusterStoreMigrationInjectable;
@@ -0,0 +1,60 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Fix embedded kubeconfig paths under snap config
import { getInjectable } from "@ogre-tools/injectable";
import applicationInformationInjectable from "../../../common/vars/application-information.injectable";
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
import loggerInjectable from "../../../common/logger.injectable";
import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable";
import type { ClusterModel } from "../../../common/cluster-types";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
const clusterStoreSnapMigrationInjectable = getInjectable({
id: "cluster-store-snap-migration",
instantiate: (di) => {
const { version } = di.inject(applicationInformationInjectable);
const logger = di.inject(loggerInjectable);
const isSnapPackage = di.inject(isSnapPackageInjectable);
const pathExistsSync = di.inject(pathExistsSyncInjectable);
return {
version, // Run always after upgrade
run(store) {
if (!isSnapPackage) {
return;
}
logger.info("Migrating embedded kubeconfig paths");
const storedClusters = (store.get("clusters") || []) as ClusterModel[];
if (!storedClusters.length) return;
logger.info("Number of clusters to migrate: ", storedClusters.length);
const migratedClusters = storedClusters
.map(cluster => {
/**
* replace snap version with 'current' in kubeconfig path
*/
if (!pathExistsSync(cluster.kubeConfigPath)) {
const kubeconfigPath = cluster.kubeConfigPath.replace(/\/snap\/kontena-lens\/[0-9]*\//, "/snap/kontena-lens/current/");
cluster.kubeConfigPath = kubeconfigPath;
}
return cluster;
});
store.set("clusters", migratedClusters);
},
};
},
injectionToken: clusterStoreMigrationInjectionToken,
});
export default clusterStoreSnapMigrationInjectable;
@@ -8,4 +8,5 @@ import electronAppInjectable from "./electron-app.injectable";
export default getGlobalOverride(electronAppInjectable, () => ({
getVersion: () => "6.0.0",
} as Electron.App));
setLoginItemSettings: () => {},
} as Partial<Electron.App> as Electron.App));
@@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Settings } from "electron";
import electronAppInjectable from "../electron-app.injectable";
export type SetLoginItemSettings = (settings: Settings) => void;
const setLoginItemSettingsInjectable = getInjectable({
id: "set-login-item-settings",
instantiate: (di): SetLoginItemSettings => {
const electronApp = di.inject(electronAppInjectable);
return (settings) => electronApp.setLoginItemSettings(settings);
},
});
export default setLoginItemSettingsInjectable;
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverrideForFunction } from "../../../common/test-utils/get-global-override-for-function";
import showOpenDialogInjectable from "./show-open-dialog.injectable";
export default getGlobalOverrideForFunction(showOpenDialogInjectable);
@@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { OpenDialogOptions } from "electron";
import { dialog } from "electron";
export type ShowOpenDialog = (options: OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>;
const showOpenDialogInjectable = getInjectable({
id: "show-open-dialog",
instantiate: (): ShowOpenDialog => opts => dialog.showOpenDialog(opts),
causesSideEffects: true,
});
export default showOpenDialogInjectable;
@@ -7,11 +7,10 @@ import { setupIpcMainHandlers } from "./setup-ipc-main-handlers";
import loggerInjectable from "../../../../common/logger.injectable";
import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import operatingSystemThemeInjectable from "../../../theme/operating-system-theme.injectable";
import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable";
import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable";
import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable";
import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable";
const setupIpcMainHandlersInjectable = getInjectable({
id: "setup-ipc-main-handlers",
@@ -21,9 +20,8 @@ const setupIpcMainHandlersInjectable = getInjectable({
const applicationMenuItemComposite = di.inject(applicationMenuItemCompositeInjectable);
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
const clusterStore = di.inject(clusterStoreInjectable);
const operatingSystemTheme = di.inject(operatingSystemThemeInjectable);
const askUserForFilePaths = di.inject(askUserForFilePathsInjectable);
const emitAppEvent = di.inject(emitAppEventInjectable);
const getClusterById = di.inject(getClusterByIdInjectable);
return {
id: "setup-ipc-main-handlers",
@@ -34,9 +32,8 @@ const setupIpcMainHandlersInjectable = getInjectable({
applicationMenuItemComposite,
catalogEntityRegistry,
clusterStore,
operatingSystemTheme,
askUserForFilePaths,
emitAppEvent,
getClusterById,
});
},
};
@@ -5,54 +5,46 @@
import type { IpcMainInvokeEvent } from "electron";
import { BrowserWindow, Menu } from "electron";
import { clusterFrameMap } from "../../../../common/cluster-frames";
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterDisconnectHandler } from "../../../../common/ipc/cluster";
import { clusterActivateHandler, clusterSetFrameIdHandler, clusterDisconnectHandler, clusterStates } from "../../../../common/ipc/cluster";
import type { ClusterId } from "../../../../common/cluster-types";
import { ClusterStore } from "../../../../common/cluster-store/cluster-store";
import type { ClusterStore } from "../../../../common/cluster-store/cluster-store";
import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc";
import type { CatalogEntityRegistry } from "../../../catalog";
import { pushCatalogToRenderer } from "../../../catalog-pusher";
import type { IComputedValue } from "mobx";
import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window";
import { handleWindowAction, onLocationChange } from "../../../ipc/window";
import { openFilePickingDialogChannel } from "../../../../common/ipc/dialog";
import { getNativeThemeChannel } from "../../../../common/ipc/native-theme";
import type { Theme } from "../../../theme/operating-system-theme-state.injectable";
import type { AskUserForFilePaths } from "../../../ipc/ask-user-for-file-paths.injectable";
import type { ApplicationMenuItemTypes } from "../../../../features/application-menu/main/menu-items/application-menu-item-injection-token";
import type { Composite } from "../../../../common/utils/composite/get-composite/get-composite";
import { getApplicationMenuTemplate } from "../../../../features/application-menu/main/populate-application-menu.injectable";
import type { MenuItemRoot } from "../../../../features/application-menu/main/application-menu-item-composite.injectable";
import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.injectable";
import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable";
interface Dependencies {
applicationMenuItemComposite: IComputedValue<Composite<ApplicationMenuItemTypes | MenuItemRoot>>;
catalogEntityRegistry: CatalogEntityRegistry;
clusterStore: ClusterStore;
operatingSystemTheme: IComputedValue<Theme>;
askUserForFilePaths: AskUserForFilePaths;
emitAppEvent: EmitAppEvent;
getClusterById: GetClusterById;
}
export const setupIpcMainHandlers = ({
applicationMenuItemComposite,
catalogEntityRegistry,
clusterStore,
operatingSystemTheme,
askUserForFilePaths,
emitAppEvent,
getClusterById,
}: Dependencies) => {
ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
return ClusterStore.getInstance()
.getById(clusterId)
return getClusterById(clusterId)
?.activate(force);
});
ipcMainHandle(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => {
const cluster = ClusterStore.getInstance().getById(clusterId);
const cluster = getClusterById(clusterId);
if (cluster) {
clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId });
cluster.pushState();
pushCatalogToRenderer(catalogEntityRegistry);
}
@@ -60,7 +52,7 @@ export const setupIpcMainHandlers = ({
ipcMainHandle(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
emitAppEvent({ name: "cluster", action: "stop" });
const cluster = ClusterStore.getInstance().getById(clusterId);
const cluster = getClusterById(clusterId);
if (cluster) {
cluster.disconnect();
@@ -72,8 +64,6 @@ export const setupIpcMainHandlers = ({
ipcMainOn(windowLocationChangedChannel, () => onLocationChange());
ipcMainHandle(openFilePickingDialogChannel, (event, opts) => askUserForFilePaths(opts));
ipcMainHandle(broadcastMainChannel, (event, channel, ...args) => broadcastMessage(channel, ...args));
ipcMainOn(windowOpenAppMenuAsContextMenuChannel, async (event) => {
@@ -88,9 +78,10 @@ export const setupIpcMainHandlers = ({
});
});
ipcMainHandle(getNativeThemeChannel, () => {
return operatingSystemTheme.get();
});
clusterStore.provideInitialFromMain();
ipcMainHandle(clusterStates, () => (
clusterStore.clustersList.map(cluster => ({
id: cluster.id,
state: cluster.getState(),
}))
));
};
-23
View File
@@ -12,13 +12,6 @@ import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
import readFileInjectable from "../common/fs/read-file.injectable";
import loggerInjectable from "../common/logger.injectable";
import spawnInjectable from "./child-process/spawn.injectable";
import extensionsStoreInjectable from "../extensions/extensions-store/extensions-store.injectable";
import type { ExtensionsStore } from "../extensions/extensions-store/extensions-store";
import fileSystemProvisionerStoreInjectable from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable";
import type { FileSystemProvisionerStore } from "../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store";
import userStoreInjectable from "../common/user-store/user-store.injectable";
import type { UserStore } from "../common/user-store";
import hotbarStoreInjectable from "../common/hotbars/store.injectable";
import commandLineArgumentsInjectable from "./utils/command-line-arguments.injectable";
import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable";
import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable";
@@ -47,7 +40,6 @@ import clusterFramesInjectable from "../common/cluster-frames.injectable";
import type { ClusterFrameInfo } from "../common/cluster-frames";
import { observable, runInAction } from "mobx";
import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable";
import setupListenerForCurrentClusterFrameInjectable from "./start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable";
import setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable";
import broadcastMessageInjectable from "../common/ipc/broadcast-message.injectable";
import getElectronThemeInjectable from "./electron-app/features/get-electron-theme.injectable";
@@ -74,7 +66,6 @@ import rollbackHelmReleaseInjectable from "./helm/helm-service/rollback-helm-rel
import waitUntilBundledExtensionsAreLoadedInjectable from "./start-main-application/lens-window/application-window/wait-until-bundled-extensions-are-loaded.injectable";
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
import type { HotbarStore } from "../common/hotbars/store";
import focusApplicationInjectable from "./electron-app/features/focus-application.injectable";
import kubectlDownloadingNormalizedArchInjectable from "./kubectl/normalized-arch.injectable";
import initializeClusterManagerInjectable from "./cluster/initialize-manager.injectable";
@@ -121,19 +112,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
di.override(waitUntilBundledExtensionsAreLoadedInjectable, () => async () => {});
di.override(getRandomIdInjectable, () => () => "some-irrelevant-random-id");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(hotbarStoreInjectable, () => ({
load: () => {},
getActive: () => ({ name: "some-hotbar", items: [] }),
getDisplayIndex: () => "0",
}) as unknown as HotbarStore);
di.override(userStoreInjectable, () => ({
startMainReactions: () => {},
extensionRegistryUrl: { customUrl: "some-custom-url" },
load: () => {},
}) as Partial<UserStore> as UserStore);
di.override(extensionsStoreInjectable, () => ({ isEnabled: (opts) => (void opts, false) }) as ExtensionsStore);
di.override(fileSystemProvisionerStoreInjectable, () => ({}) as FileSystemProvisionerStore);
overrideOperatingSystem(di);
overrideRunnablesHavingSideEffects(di);
@@ -199,7 +177,6 @@ const overrideRunnablesHavingSideEffects = (di: DiContainer) => {
setupShellInjectable,
setupSyncingOfWeblinksInjectable,
setupSystemCaInjectable,
setupListenerForCurrentClusterFrameInjectable,
setupRunnablesAfterWindowIsOpenedInjectable,
startCatalogSyncInjectable,
startKubeConfigSyncInjectable,
+15 -4
View File
@@ -41,10 +41,17 @@ describe("Helm Service tests", () => {
getActiveHelmRepositoriesMock.mockReturnValue(
Promise.resolve({
callWasSuccessful: true,
response: [
{ name: "stable", url: "stableurl" },
{ name: "experiment", url: "experimenturl" },
{
name: "stable",
url: "stableurl",
cacheFilePath: "/some-cache-file-path-for-stable",
},
{
name: "experiment",
url: "experimenturl",
cacheFilePath: "/some-cache-file-path-for-experiment",
},
],
}),
);
@@ -152,7 +159,11 @@ describe("Helm Service tests", () => {
getActiveHelmRepositoriesMock.mockReturnValue(
Promise.resolve({
callWasSuccessful: true,
response: [{ name: "bitnami", url: "bitnamiurl" }],
response: [{
name: "bitnami",
url: "bitnamiurl",
cacheFilePath: "/some-cache-file-path-for-bitnami",
}],
}),
);
@@ -4,16 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import httpsProxyConfigurationInjectable from "../../../common/user-store/https-proxy.injectable";
const execHelmEnvInjectable = getInjectable({
id: "exec-helm-env",
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
const httpsProxyConfiguration = di.inject(httpsProxyConfigurationInjectable);
return computed(() => {
const {
HTTPS_PROXY = userStore.httpsProxy,
HTTPS_PROXY = httpsProxyConfiguration.get(),
...env
} = process.env;
-45
View File
@@ -1,45 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { promiseExecFile } from "../../common/utils/promise-exec";
import type { ObjectEncodingOptions } from "fs";
import type { ExecFileOptions, ExecFileOptionsWithStringEncoding } from "child_process";
import { UserStore } from "../../common/user-store";
import { isChildProcessError } from "../../common/utils";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import helmBinaryPathInjectable from "./helm-binary-path.injectable";
/**
* ExecFile the bundled helm CLI
* @returns STDOUT
*/
export async function execHelm(args: string[], { encoding, ...rest }: ObjectEncodingOptions & ExecFileOptions = {}): Promise<string> {
const options: ExecFileOptionsWithStringEncoding = {
encoding: encoding ?? "utf-8",
...rest,
};
const di = getLegacyGlobalDiForExtensionApi();
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
try {
const opts = { ...options };
opts.env ??= { ...process.env };
if (!opts.env.HTTPS_PROXY && UserStore.getInstance().httpsProxy) {
opts.env.HTTPS_PROXY = UserStore.getInstance().httpsProxy;
}
const { stdout } = await promiseExecFile(helmBinaryPath, args, opts);
return stdout;
} catch (error) {
if (isChildProcessError(error, "string")) {
throw error.stderr || error;
}
throw error;
}
}
+10 -6
View File
@@ -7,16 +7,20 @@ import type { HelmRepo } from "../../common/helm/helm-repo";
import { HelmChartManager } from "./helm-chart-manager";
import helmChartManagerCacheInjectable from "./helm-chart-manager-cache.injectable";
import loggerInjectable from "../../common/logger.injectable";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
import readFileInjectable from "../../common/fs/read-file.injectable";
import statInjectable from "../../common/fs/stat.injectable";
const helmChartManagerInjectable = getInjectable({
id: "helm-chart-manager",
instantiate: (di, repo: HelmRepo) => {
const cache = di.inject(helmChartManagerCacheInjectable);
const logger = di.inject(loggerInjectable);
return new HelmChartManager(repo, { cache, logger });
},
instantiate: (di, repo: HelmRepo) => new HelmChartManager({
cache: di.inject(helmChartManagerCacheInjectable),
logger: di.inject(loggerInjectable),
execHelm: di.inject(execHelmInjectable),
readFile: di.inject(readFileInjectable),
stat: di.inject(statInjectable),
}, repo),
lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, repo: HelmRepo) => repo.name,
+16 -17
View File
@@ -3,16 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import fs from "fs";
import * as yaml from "js-yaml";
import { iter, put, sortBySemverVersion } from "../../common/utils";
import { execHelm } from "./exec";
import type { SetRequired } from "type-fest";
import { assert } from "console";
import type { HelmRepo } from "../../common/helm/helm-repo";
import type { HelmChartManagerCache } from "./helm-chart-manager-cache.injectable";
import type { Logger } from "../../common/logger";
import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api/request-charts.injectable";
import type { ExecHelm } from "./exec-helm/exec-helm.injectable";
import type { ReadFile } from "../../common/fs/read-file.injectable";
import type { Stat } from "../../common/fs/stat.injectable";
export interface HelmCacheFile {
apiVersion: string;
@@ -20,18 +19,18 @@ export interface HelmCacheFile {
}
interface Dependencies {
cache: HelmChartManagerCache;
logger: Logger;
readonly cache: HelmChartManagerCache;
readonly logger: Logger;
execHelm: ExecHelm;
readFile: ReadFile;
stat: Stat;
}
export class HelmChartManager {
protected readonly repo: SetRequired<HelmRepo, "cacheFilePath">;
constructor(repo: HelmRepo, private dependencies: Dependencies) {
assert(repo.cacheFilePath, "CacheFilePath must be provided on the helm repo");
this.repo = repo as SetRequired<HelmRepo, "cacheFilePath">;
}
constructor(
private readonly dependencies: Dependencies,
protected readonly repo: HelmRepo,
) {}
public async chartVersions(name: string) {
const charts = await this.charts();
@@ -56,7 +55,7 @@ export class HelmChartManager {
args.push("--version", version);
}
return execHelm(args);
return this.dependencies.execHelm(args);
}
public async getReadme(name: string, version?: string) {
@@ -68,8 +67,8 @@ export class HelmChartManager {
}
protected async updateYamlCache() {
const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8");
const cacheFileStats = await fs.promises.stat(this.repo.cacheFilePath);
const cacheFile = await this.dependencies.readFile(this.repo.cacheFilePath);
const cacheFileStats = await this.dependencies.stat(this.repo.cacheFilePath);
const data = yaml.load(cacheFile) as string | number | HelmCacheFile;
if (!data || typeof data !== "object" || typeof data.entries !== "object") {
@@ -94,7 +93,7 @@ export class HelmChartManager {
if (!cacheEntry) {
cacheEntry = await this.updateYamlCache();
} else {
const newStats = await fs.promises.stat(this.repo.cacheFilePath);
const newStats = await this.dependencies.stat(this.repo.cacheFilePath);
if (cacheEntry.mtimeMs < newStats.mtimeMs) {
cacheEntry = await this.updateYamlCache();
+49 -12
View File
@@ -7,9 +7,12 @@ import tempy from "tempy";
import fse from "fs-extra";
import * as yaml from "js-yaml";
import { toCamelCase } from "../../common/utils/camelCase";
import { execHelm } from "./exec";
import type { JsonValue } from "type-fest";
import { isObject, json } from "../../common/utils";
import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
import execHelmInjectable from "./exec-helm/exec-helm.injectable";
const execHelm = asLegacyGlobalFunctionForExtensionApi(execHelmInjectable);
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
const args = [
@@ -26,7 +29,13 @@ export async function listReleases(pathToKubeconfig: string, namespace?: string)
args.push("--kubeconfig", pathToKubeconfig);
const output = json.parse(await execHelm(args));
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = json.parse(result.response);
if (!Array.isArray(output) || output.length == 0) {
return [];
@@ -60,7 +69,13 @@ export async function installChart(chart: string, values: JsonValue, name: strin
}
try {
const output = await execHelm(args);
const result = await execHelm(args);
if (!result.callWasSuccessful) {
throw result.error;
}
const output = result.response;
const releaseName = output.split("\n")[0].split(" ")[1].trim();
return {
@@ -75,13 +90,19 @@ export async function installChart(chart: string, values: JsonValue, name: strin
}
}
export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string) {
return execHelm([
export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string): Promise<string> {
const result = await execHelm([
"delete",
name,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
}
interface GetValuesOptions {
@@ -90,7 +111,7 @@ interface GetValuesOptions {
kubeconfigPath: string;
}
export async function getValues(name: string, { namespace, all = false, kubeconfigPath }: GetValuesOptions) {
export async function getValues(name: string, { namespace, all = false, kubeconfigPath }: GetValuesOptions): Promise<string> {
const args = [
"get",
"values",
@@ -107,25 +128,41 @@ export async function getValues(name: string, { namespace, all = false, kubeconf
"--kubeconfig", kubeconfigPath,
);
return execHelm(args);
const result = await execHelm(args);
if (result.callWasSuccessful) {
return result.response;
}
throw result.error;
}
export async function getHistory(name: string, namespace: string, kubeconfigPath: string) {
return json.parse(await execHelm([
export async function getHistory(name: string, namespace: string, kubeconfigPath: string): Promise<JsonValue> {
const result = await execHelm([
"history",
name,
"--output", "json",
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]));
]);
if (result.callWasSuccessful) {
return json.parse(result.response);
}
throw result.error;
}
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) {
await execHelm([
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string): Promise<void> {
const result = await execHelm([
"rollback",
name,
`${revision}`,
"--namespace", namespace,
"--kubeconfig", kubeconfigPath,
]);
if (!result.callWasSuccessful) {
throw result.error;
}
}
@@ -8,7 +8,7 @@ import loggerInjectable from "../../../common/logger.injectable";
import tempy from "tempy";
import getHelmReleaseInjectable from "./get-helm-release.injectable";
import writeFileInjectable from "../../../common/fs/write-file.injectable";
import removePathInjectable from "../../../common/fs/remove-path.injectable";
import removePathInjectable from "../../../common/fs/remove.injectable";
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
export interface UpdateChartArgs {
@@ -0,0 +1,34 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import { getEmptyHotbar } from "../../../common/hotbars/types";
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.0-hotbar-store-migration",
instantiate: (di) => {
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
return {
version: "5.0.0-alpha.0",
run(store) {
const hotbar = getEmptyHotbar("default");
const { metadata: { uid, name, source }} = catalogCatalogEntity;
hotbar.items[0] = { entity: { uid, name, source }};
store.set("hotbars", [hotbar]);
},
};
},
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha0HotbarStoreMigrationInjectable;
@@ -0,0 +1,30 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Cleans up a store that had the state related data stored
import type { Hotbar } from "../../../common/hotbars/types";
import * as uuid from "uuid";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.2-hotbar-store-migration",
instantiate: () => ({
version: "5.0.0-alpha.2",
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : [];
store.set("hotbars", hotbars.map(({ id, ...rest }) => ({
id: id || uuid.v4(),
...rest,
})));
},
}),
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Alpha2HotbarStoreMigrationInjectable;
@@ -0,0 +1,173 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import * as uuid from "uuid";
import type { ClusterStoreModel } from "../../../common/cluster-store/cluster-store";
import type { Hotbar, HotbarItem } from "../../../common/hotbars/types";
import { defaultHotbarCells, getEmptyHotbar } from "../../../common/hotbars/types";
import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable";
import { isDefined, isErrnoException } from "../../../common/utils";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import { generateNewIdFor } from "../../../common/utils/generate-new-id-for";
interface Pre500WorkspaceStoreModel {
workspaces: {
id: string;
name: string;
}[];
}
interface PartialHotbar {
id: string;
name: string;
items: (null | HotbarItem)[];
}
const v500Beta10HotbarStoreMigrationInjectable = getInjectable({
id: "v5.0.0-beta.10-hotbar-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const logger = di.inject(loggerInjectable);
return {
version: "5.0.0-beta.10",
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : [];
// Hotbars might be empty, if some of the previous migrations weren't run
if (hotbars.length === 0) {
const hotbar = getEmptyHotbar("default");
const { metadata: { uid, name, source }} = catalogCatalogEntity;
hotbar.items[0] = { entity: { uid, name, source }};
hotbars.push(hotbar);
}
try {
const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
const { clusters = [] }: ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"));
const workspaceHotbars = new Map<string, PartialHotbar>(); // mapping from WorkspaceId to HotBar
for (const { id, name } of workspaceStoreData.workspaces) {
logger.info(`Creating new hotbar for ${name}`);
workspaceHotbars.set(id, {
id: uuid.v4(), // don't use the old IDs as they aren't necessarily UUIDs
items: [],
name: `Workspace: ${name}`,
});
}
{
// grab the default named hotbar or the first.
const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default"));
const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1);
workspaceHotbars.set("default", {
name,
id,
items: items.filter(isDefined),
});
}
for (const cluster of clusters) {
const uid = generateNewIdFor(cluster);
for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) {
const workspaceHotbar = workspaceHotbars.get(workspaceId);
if (!workspaceHotbar) {
logger.info(`Cluster ${uid} has unknown workspace ID, skipping`);
continue;
}
logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`);
if (workspaceHotbar?.items.length < defaultHotbarCells) {
workspaceHotbar.items.push({
entity: {
uid: generateNewIdFor(cluster),
name: cluster.preferences?.clusterName || cluster.contextName,
},
});
}
}
}
for (const hotbar of workspaceHotbars.values()) {
if (hotbar.items.length === 0) {
logger.info(`Skipping ${hotbar.name} due to it being empty`);
continue;
}
while (hotbar.items.length < defaultHotbarCells) {
hotbar.items.push(null);
}
hotbars.push(hotbar as Hotbar);
}
/**
* Finally, make sure that the catalog entity hotbar item is in place.
* Just in case something else removed it.
*
* if every hotbar has elements that all not the `catalog-entity` item
*/
if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) {
// note, we will add a new whole hotbar here called "default" if that was previously removed
const di = getLegacyGlobalDiForExtensionApi();
const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable);
const defaultHotbar = hotbars.find(hotbar => hotbar.name === "default");
const { metadata: { uid, name, source }} = catalogCatalogEntity;
if (defaultHotbar) {
const freeIndex = defaultHotbar.items.findIndex(i => i === null);
if (freeIndex === -1) {
// making a new hotbar is less destructive if the first hotbar
// called "default" is full than overriding a hotbar item
const hotbar = getEmptyHotbar("initial");
hotbar.items[0] = { entity: { uid, name, source }};
hotbars.unshift(hotbar);
} else {
defaultHotbar.items[freeIndex] = { entity: { uid, name, source }};
}
} else {
const hotbar = getEmptyHotbar("default");
hotbar.items[0] = { entity: { uid, name, source }};
hotbars.unshift(hotbar);
}
}
} catch (error) {
// ignore files being missing
if (isErrnoException(error) && error.code !== "ENOENT") {
throw error;
}
}
store.set("hotbars", hotbars);
},
};
},
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta10HotbarStoreMigrationInjectable;
@@ -0,0 +1,54 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Hotbar } from "../../../common/hotbars/types";
import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token";
const v500Beta5HotbarStoreMigrationInjectable = getInjectable({
id: "v500-beta5-hotbar-store-migration",
instantiate: (di) => {
const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable);
return {
version: "5.0.0-beta.5",
run(store) {
const rawHotbars = store.get("hotbars");
const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : [];
for (const hotbar of hotbars) {
for (let i = 0; i < hotbar.items.length; i += 1) {
const item = hotbar.items[i];
if (!item) {
continue;
}
const entity = catalogEntityRegistry.findById(item.entity.uid);
if (!entity) {
// Clear disabled item
hotbar.items[i] = null;
} else {
// Save additional data
item.entity = {
...item.entity,
name: entity.metadata.name,
source: entity.metadata.source,
};
}
}
}
store.set("hotbars", hotbars);
},
};
},
injectionToken: hotbarStoreMigrationInjectionToken,
});
export default v500Beta5HotbarStoreMigrationInjectable;
+10 -1
View File
@@ -7,14 +7,23 @@
import * as Mobx from "mobx";
import { spawn } from "node-pty";
import process from "process";
import * as LensExtensionsCommonApi from "../extensions/common-api";
import * as LensExtensionsMainApi from "../extensions/main-api";
import { getDi } from "./getDi";
import startMainApplicationInjectable from "./start-main-application/start-main-application.injectable";
const di = getDi();
const startMainApplication = di.inject(startMainApplicationInjectable);
void di.inject(startMainApplicationInjectable);
(async () => {
try {
await startMainApplication();
} catch (error) {
console.error(error);
process.exit(1);
}
})();
/**
* Exports for virtual package "@k8slens/extensions" for main-process.
@@ -3,34 +3,41 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { OpenDialogOptions } from "electron";
import { dialog } from "electron";
import { getInjectable } from "@ogre-tools/injectable";
import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable";
import type { RequestChannelHandler } from "../utils/channel/channel-listeners/listener-tokens";
import type { openPathPickingDialogChannel } from "../../features/path-picking-dialog/common/channel";
import showOpenDialogInjectable from "../electron-app/features/show-open-dialog.injectable";
// TODO: Replace leaking electron with abstraction
export type AskUserForFilePaths = (
dialogOptions: OpenDialogOptions
) => Promise<{ canceled: boolean; filePaths: string[] }>;
export type AskUserForFilePaths = RequestChannelHandler<typeof openPathPickingDialogChannel>;
const askUserForFilePathsInjectable = getInjectable({
id: "ask-user-for-file-paths",
instantiate: (di): AskUserForFilePaths => {
const showApplicationWindow = di.inject(showApplicationWindowInjectable);
const showOpenDialog = di.inject(showOpenDialogInjectable);
return async (dialogOptions) => {
await showApplicationWindow();
const { canceled, filePaths } = await dialog.showOpenDialog(
const { canceled, filePaths } = await showOpenDialog(
dialogOptions,
);
return { canceled, filePaths };
if (canceled) {
return {
canceled,
};
}
return {
canceled: false,
paths: filePaths,
};
};
},
causesSideEffects: true,
});
export default askUserForFilePathsInjectable;
@@ -11,9 +11,9 @@ import loggerInjectable from "../../common/logger.injectable";
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
import joinPathsInjectable from "../../common/path/join-paths.injectable";
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
import pathExistsInjectable from "../../common/fs/path-exists.injectable";
import writeFileInjectable from "../../common/fs/write-file.injectable";
import removePathInjectable from "../../common/fs/remove.injectable";
export interface KubeConfigManagerInstantiationParameter {
cluster: Cluster;
@@ -31,7 +31,7 @@ const createKubeconfigManagerInjectable = getInjectable({
lensProxyPort: di.inject(lensProxyPortInjectable),
joinPaths: di.inject(joinPathsInjectable),
getDirnameOfPath: di.inject(getDirnameOfPathInjectable),
deleteFile: di.inject(deleteFileInjectable),
removePath: di.inject(removePathInjectable),
pathExists: di.inject(pathExistsInjectable),
writeFile: di.inject(writeFileInjectable),
};
@@ -13,7 +13,7 @@ import type { Logger } from "../../common/logger";
import type { JoinPaths } from "../../common/path/join-paths.injectable";
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
import type { PathExists } from "../../common/fs/path-exists.injectable";
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
import type { RemovePath } from "../../common/fs/remove.injectable";
import type { WriteFile } from "../../common/fs/write-file.injectable";
export interface KubeconfigManagerDependencies {
@@ -23,7 +23,7 @@ export interface KubeconfigManagerDependencies {
joinPaths: JoinPaths;
getDirnameOfPath: GetDirnameOfPath;
pathExists: PathExists;
deleteFile: DeleteFile;
removePath: RemovePath;
writeFile: WriteFile;
}
@@ -65,7 +65,7 @@ export class KubeconfigManager {
this.dependencies.logger.info(`[KUBECONFIG-MANAGER]: Deleting temporary kubeconfig: ${this.tempFilePath}`);
try {
await this.dependencies.deleteFile(this.tempFilePath);
await this.dependencies.removePath(this.tempFilePath);
} catch (error) {
if (isErrnoException(error) && error.code !== "ENOENT") {
throw error;
@@ -19,6 +19,10 @@ import type { ObservableMap } from "mobx";
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
import pathExistsInjectable from "../../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
function throwIfDefined(val: any): void {
if (val != null) {
@@ -35,6 +39,11 @@ describe("protocol router tests", () => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
enabledExtensions = new Set();
di.override(extensionsStoreInjectable, () => ({
@@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import deleteFileInjectable from "../../common/fs/delete-file.injectable";
import removePathInjectable from "../../common/fs/remove.injectable";
import execFileInjectable from "../../common/fs/exec-file.injectable";
import writeFileInjectable from "../../common/fs/write-file.injectable";
import loggerInjectable from "../../common/logger.injectable";
@@ -19,7 +19,7 @@ const createResourceApplierInjectable = getInjectable({
id: "create-resource-applier",
instantiate: (di): CreateResourceApplier => {
const deps: ResourceApplierDependencies = {
deleteFile: di.inject(deleteFileInjectable),
deleteFile: di.inject(removePathInjectable),
emitAppEvent: di.inject(emitAppEventInjectable),
execFile: di.inject(execFileInjectable),
joinPaths: di.inject(joinPathsInjectable),
@@ -11,7 +11,7 @@ import type { KubernetesObject } from "@kubernetes/client-node";
import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable";
import type { Logger } from "../../common/logger";
import type { WriteFile } from "../../common/fs/write-file.injectable";
import type { DeleteFile } from "../../common/fs/delete-file.injectable";
import type { RemovePath } from "../../common/fs/remove.injectable";
import type { ExecFile } from "../../common/fs/exec-file.injectable";
import type { JoinPaths } from "../../common/path/join-paths.injectable";
import type { AsyncResult } from "../../common/utils/async-result";
@@ -19,7 +19,7 @@ import type { AsyncResult } from "../../common/utils/async-result";
export interface ResourceApplierDependencies {
emitAppEvent: EmitAppEvent;
writeFile: WriteFile;
deleteFile: DeleteFile;
deleteFile: RemovePath;
execFile: ExecFile;
joinPaths: JoinPaths;
readonly logger: Logger;
@@ -23,7 +23,7 @@ import userShellSettingInjectable from "../../../common/user-store/shell-setting
import appNameInjectable from "../../../common/vars/app-name.injectable";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import statInjectable from "../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
export interface OpenLocalShellSessionArgs {
websocket: WebSocket;
@@ -7,6 +7,11 @@ import type { DiContainer } from "@ogre-tools/injectable";
import { WebSocket } from "ws";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { Cluster } from "../../../common/cluster/cluster";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
import pathExistsInjectable from "../../../common/fs/path-exists.injectable";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable";
import platformInjectable from "../../../common/vars/platform.injectable";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import createKubectlInjectable from "../../kubectl/create-kubectl.injectable";
@@ -29,6 +34,11 @@ describe("technical unit tests for local shell sessions", () => {
di.override(buildVersionInjectable, () => ({
get: () => "1.1.1",
}));
di.override(pathExistsInjectable, () => () => { throw new Error("tried call pathExists without override"); });
di.override(pathExistsSyncInjectable, () => () => { throw new Error("tried call pathExistsSync without override"); });
di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); });
di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); });
di.override(statInjectable, () => () => { throw new Error("tried call stat without override"); });
});
describe("when on windows", () => {
@@ -18,7 +18,7 @@ import userShellSettingInjectable from "../../../common/user-store/shell-setting
import appNameInjectable from "../../../common/vars/app-name.injectable";
import buildVersionInjectable from "../../vars/build-version/build-version.injectable";
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
import statInjectable from "../../../common/fs/stat/stat.injectable";
import statInjectable from "../../../common/fs/stat.injectable";
export interface NodeShellSessionArgs {
websocket: WebSocket;
+1 -1
View File
@@ -17,7 +17,7 @@ import type { ComputeShellEnvironment } from "../../features/shell-sync/main/com
import type { SpawnPty } from "./spawn-pty.injectable";
import type { InitializableState } from "../../common/initializable-state/create";
import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable";
import type { Stat } from "../../common/fs/stat/stat.injectable";
import type { Stat } from "../../common/fs/stat.injectable";
import type { IComputedValue } from "mobx";
export class ShellOpenError extends Error {
@@ -12,8 +12,8 @@ import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-b
import getAbsolutePathInjectable from "../../../../common/path/get-absolute-path.injectable";
import lensResourcesDirInjectable from "../../../../common/vars/lens-resources-dir.injectable";
import isLinuxInjectable from "../../../../common/vars/is-linux.injectable";
import fsInjectable from "../../../../common/fs/fs.injectable";
import applicationInformationInjectable from "../../../../common/vars/application-information.injectable";
import pathExistsSyncInjectable from "../../../../common/fs/path-exists-sync.injectable";
export type ElectronWindowTitleBarStyle = "hiddenInset" | "hidden" | "default" | "customButtonsOnHover";
@@ -55,6 +55,7 @@ const createElectronWindowInjectable = getInjectable({
const lensResourcesDir = di.inject(lensResourcesDirInjectable);
const isLinux = di.inject(isLinuxInjectable);
const applicationInformation = di.inject(applicationInformationInjectable);
const pathExistsSync = di.inject(pathExistsSyncInjectable);
return (configuration) => {
const applicationWindowState = di.inject(
@@ -93,7 +94,7 @@ const createElectronWindowInjectable = getInjectable({
const iconFileName = [
getAbsolutePath(lensResourcesDir, `../${applicationInformation.name}.png`),
`/usr/share/icons/hicolor/512x512/apps/${applicationInformation.name}.png`,
].find(di.inject(fsInjectable).existsSync);
].find(pathExistsSync);
if (iconFileName != null) {
try {
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { currentClusterMessageChannel } from "../../../../common/cluster/current-cluster-channel";
import { getMessageChannelListenerInjectable } from "../../../../common/utils/channel/message-channel-listener-injection-token";
import currentClusterFrameClusterIdStateInjectable from "./current-cluster-frame-cluster-id-state.injectable";
const currentVisibileClusterListenerInjectable = getMessageChannelListenerInjectable({
id: "current-visibile-cluster",
channel: currentClusterMessageChannel,
handler: (di) => {
const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable);
return clusterId => currentClusterFrameState.set(clusterId);
},
});
export default currentVisibileClusterListenerInjectable;
@@ -1,35 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { ipcMainOn } from "../../../../common/ipc";
import { IpcRendererNavigationEvents } from "../../../../renderer/navigation/events";
import type { ClusterId } from "../../../../common/cluster-types";
import { getInjectable } from "@ogre-tools/injectable";
import { onLoadOfApplicationInjectionToken } from "../../runnable-tokens/on-load-of-application-injection-token";
import currentClusterFrameClusterIdStateInjectable from "./current-cluster-frame-cluster-id-state.injectable";
const setupListenerForCurrentClusterFrameInjectable = getInjectable({
id: "setup-listener-for-current-cluster-frame",
instantiate: (di) => ({
id: "setup-listener-for-current-cluster-frame",
run: () => {
const currentClusterFrameState = di.inject(currentClusterFrameClusterIdStateInjectable);
ipcMainOn(
IpcRendererNavigationEvents.CLUSTER_VIEW_CURRENT_ID,
(event, clusterId: ClusterId) => {
currentClusterFrameState.set(clusterId);
},
);
},
}),
causesSideEffects: true,
injectionToken: onLoadOfApplicationInjectionToken,
});
export default setupListenerForCurrentClusterFrameInjectable;
@@ -8,6 +8,8 @@ import clusterFramesInjectable from "../../../common/cluster-frames.injectable";
import showApplicationWindowInjectable from "./show-application-window.injectable";
import getCurrentApplicationWindowInjectable from "./application-window/get-current-application-window.injectable";
import assert from "assert";
import type { NavigateForExtensionArgs } from "../../../features/extensions/navigate/common/channel";
import { navigateForExtensionChannel } from "../../../features/extensions/navigate/common/channel";
export type NavigateForExtension = (
extId: string,
@@ -42,9 +44,9 @@ const navigateForExtensionInjectable = getInjectable({
);
applicationWindow.send({
channel: "extension:navigate",
channel: navigateForExtensionChannel.id,
frameInfo,
data: [extId, pageId, params],
data: { extId, pageId, params } as NavigateForExtensionArgs,
});
};
},
@@ -1,27 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import setupSyncingOfGeneralCatalogEntitiesInjectable from "./setup-syncing-of-general-catalog-entities.injectable";
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
import hotbarStoreInjectable from "../../../common/hotbars/store.injectable";
const setupHotbarStoreInjectable = getInjectable({
id: "setup-hotbar-store",
instantiate: (di) => ({
id: "setup-hotbar-store",
run: () => {
const hotbarStore = di.inject(hotbarStoreInjectable);
hotbarStore.load();
},
runAfter: di.inject(setupSyncingOfGeneralCatalogEntitiesInjectable),
}),
injectionToken: onLoadOfApplicationInjectionToken,
});
export default setupHotbarStoreInjectable;
@@ -1,26 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import userStoreInjectable from "../../../common/user-store/user-store.injectable";
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
const setupReactionsInUserStoreInjectable = getInjectable({
id: "setup-reactions-in-user-store",
instantiate: (di) => {
const userStore = di.inject(userStoreInjectable);
return {
id: "setup-reactions-in-user-store",
run: () => {
userStore.startMainReactions();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default setupReactionsInUserStoreInjectable;
@@ -29,19 +29,21 @@ const startMainApplicationInjectable = getInjectable({
const showInitialWindowRunnablePhase = runMany(showInitialWindowRunnablePhaseInjectionToken);
const afterApplicationIsLoaded = runMany(afterApplicationIsLoadedInjectionToken);
// Stuff happening before application is ready needs to be synchronous because of
// https://github.com/electron/electron/issues/21370
appPathsRunnablePhase();
beforeElectronIsReady();
return () => {
// Stuff happening before application is ready needs to be synchronous because of
// https://github.com/electron/electron/issues/21370
appPathsRunnablePhase();
beforeElectronIsReady();
return (async () => {
await waitForElectronToBeReady();
await beforeApplicationIsLoading();
await showLoadingRunnablePhase();
await onLoadOfApplication();
await showInitialWindowRunnablePhase();
await afterApplicationIsLoaded();
})();
return (async () => {
await waitForElectronToBeReady();
await beforeApplicationIsLoading();
await showLoadingRunnablePhase();
await onLoadOfApplication();
await showInitialWindowRunnablePhase();
await afterApplicationIsLoaded();
})();
};
},
});
@@ -1,27 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import { getStartableStoppable } from "../../../common/utils/get-startable-stoppable";
import { setNativeThemeChannel } from "../../../common/ipc/native-theme";
import operatingSystemThemeInjectable from "../operating-system-theme.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
const broadcastThemeChangeInjectable = getInjectable({
id: "broadcast-theme-change",
instantiate: (di) => {
const currentTheme = di.inject(operatingSystemThemeInjectable);
const broadcastMessage = di.inject(broadcastMessageInjectable);
return getStartableStoppable("broadcast-theme-change", () =>
reaction(() => currentTheme.get(), (theme) => {
broadcastMessage(setNativeThemeChannel, theme);
}),
);
},
});
export default broadcastThemeChangeInjectable;
@@ -1,26 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { onLoadOfApplicationInjectionToken } from "../../start-main-application/runnable-tokens/on-load-of-application-injection-token";
import broadcastThemeChangeInjectable from "./broadcast-theme-change.injectable";
const startBroadcastingThemeChangeInjectable = getInjectable({
id: "start-broadcasting-theme-change",
instantiate: (di) => {
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
return {
id: "start-broadcasting-theme-change",
run: async () => {
await broadcastThemeChange.start();
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default startBroadcastingThemeChangeInjectable;
@@ -1,24 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import broadcastThemeChangeInjectable from "./broadcast-theme-change.injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../start-main-application/runnable-tokens/before-quit-of-back-end-injection-token";
const stopBroadcastingThemeChangeInjectable = getInjectable({
id: "stop-broadcasting-theme-change",
instantiate: (di) => {
const broadcastThemeChange = di.inject(broadcastThemeChangeInjectable);
return {
id: "stop-broadcasting-theme-change",
run: () => void broadcastThemeChange.stop(),
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopBroadcastingThemeChangeInjectable;
@@ -14,8 +14,8 @@ const startSyncingThemeFromOperatingSystemInjectable = getInjectable({
return {
id: "start-syncing-theme-from-operating-system",
run: async () => {
await syncTheme.start();
run: () => {
syncTheme.start();
},
};
},
@@ -15,8 +15,8 @@ const startReactiveTrayMenuIconInjectable = getInjectable({
return {
id: "start-reactive-tray-menu-icon",
run: async () => {
await reactiveTrayMenuIcon.start();
run: () => {
reactiveTrayMenuIcon.start();
},
runAfter: di.inject(startTrayInjectable),
@@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
// Switch representation of hiddenTableColumns in store
import { getInjectable } from "@ogre-tools/injectable";
import { userStoreMigrationInjectionToken } from "../../../common/user-store/migrations-token";
interface PreV500Alpha3UserPreferencesModel {
hiddenTableColumns?: Record<string, string[]>;
}
const v500Alpha3UserStoreMigrationInjectable = getInjectable({
id: "v5.0.0-alpha.3-user-store-migration",
instantiate: () => ({
version: "5.0.0-alpha.3",
run(store) {
const preferences = (store.get("preferences") ?? {}) as PreV500Alpha3UserPreferencesModel;
const oldHiddenTableColumns = preferences.hiddenTableColumns;
if (!oldHiddenTableColumns) {
return;
}
store.set("preferences", {
...preferences,
hiddenTableColumns: Object.entries(oldHiddenTableColumns),
});
},
}),
injectionToken: userStoreMigrationInjectionToken,
});
export default v500Alpha3UserStoreMigrationInjectable;
@@ -0,0 +1,91 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { ClusterStoreModel } from "../../../common/cluster-store/cluster-store";
import type { KubeconfigSyncEntry, UserPreferencesModel } from "../../../common/user-store";
import { isErrnoException } from "../../../common/utils";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
import isLogicalChildPathInjectable from "../../../common/path/is-logical-child-path.injectable";
import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable";
import { getInjectable } from "@ogre-tools/injectable";
import { userStoreMigrationInjectionToken } from "../../../common/user-store/migrations-token";
import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable";
import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable";
const v503Beta1UserStoreMigrationInjectable = getInjectable({
id: "v5.0.3-beta.1-user-store-migration",
instantiate: (di) => {
const userDataPath = di.inject(directoryForUserDataInjectable);
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const logger = di.inject(loggerInjectable);
const isLogicalChildPath = di.inject(isLogicalChildPathInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
const readJsonSync = di.inject(readJsonSyncInjectable);
const homeDirectoryPath = di.inject(homeDirectoryPathInjectable);
const pathExistsSync = di.inject(pathExistsSyncInjectable);
return {
version: "5.0.3-beta.1",
run(store) {
try {
const { syncKubeconfigEntries = [], ...preferences } = (store.get("preferences") ?? {}) as UserPreferencesModel;
const { clusters = [] }: ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json"), "utf-8") ?? {};
const extensionDataDir = joinPaths(userDataPath, "extension_data");
const syncPaths = new Set(syncKubeconfigEntries.map(s => s.filePath));
syncPaths.add(joinPaths(homeDirectoryPath, ".kube"));
for (const cluster of clusters) {
if (!cluster.kubeConfigPath) {
continue;
}
const dirOfKubeconfig = getDirnameOfPath(cluster.kubeConfigPath);
if (dirOfKubeconfig === kubeConfigsPath) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is under the stored KubeConfig folder`);
continue;
}
if (syncPaths.has(cluster.kubeConfigPath) || syncPaths.has(dirOfKubeconfig)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is already being synced`);
continue;
}
if (isLogicalChildPath(extensionDataDir, cluster.kubeConfigPath)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath is placed under an extension_data folder`);
continue;
}
if (!pathExistsSync(cluster.kubeConfigPath)) {
logger.info(`Skipping ${cluster.id} because kubeConfigPath no longer exists`);
continue;
}
logger.info(`Adding ${cluster.kubeConfigPath} from ${cluster.id} to sync paths`);
syncPaths.add(cluster.kubeConfigPath);
}
const updatedSyncEntries: KubeconfigSyncEntry[] = [...syncPaths].map(filePath => ({ filePath }));
logger.info("Final list of synced paths", updatedSyncEntries);
store.set("preferences", { ...preferences, syncKubeconfigEntries: updatedSyncEntries });
} catch (error) {
if (isErrnoException(error) && error.code !== "ENOENT") {
// ignore files being missing
throw error;
}
}
},
};
},
injectionToken: userStoreMigrationInjectionToken,
});
export default v503Beta1UserStoreMigrationInjectable;
@@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { reaction } from "mobx";
import userStoreInjectable from "../../common/user-store/user-store.injectable";
import setLoginItemSettingsInjectable from "../electron-app/features/set-login-item-settings.injectable";
import { onLoadOfApplicationInjectionToken } from "../start-main-application/runnable-tokens/on-load-of-application-injection-token";
const setupSyncOpenAtLoginWithOsInjectable = getInjectable({
id: "setup-sync-open-at-login-with-os",
instantiate: (di) => {
const setLoginItemSettings = di.inject(setLoginItemSettingsInjectable);
const userStore = di.inject(userStoreInjectable);
return {
id: "setup-sync-open-at-login-with-os",
run: () => {
reaction(() => userStore.openAtLogin, openAtLogin => {
setLoginItemSettings({
openAtLogin,
openAsHidden: true,
args: ["--hidden"],
});
}, {
fireImmediately: true,
});
},
};
},
injectionToken: onLoadOfApplicationInjectionToken,
});
export default setupSyncOpenAtLoginWithOsInjectable;
+24
View File
@@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export const lensWebsiteWeblinkId = "lens-website-link";
export const lensWebsiteLinkName = "Lens Website";
export const lensDocumentationWeblinkId = "lens-documentation-link";
export const lensDocumentationWeblinkName = "Lens Documentation";
export const lensSlackWeblinkId = "lens-slack-link";
export const lensSlackWeblinkName = "Lens Community Slack";
export const lensTwitterWeblinkId = "lens-twitter-link";
export const lensTwitterWeblinkName = "Lens on Twitter";
export const lensBlogWeblinkId = "lens-blog-link";
export const lensBlogWeblinkName = "Lens Official Blog";
export const kubernetesDocumentationWeblinkId = "kubernetes-documentation-link";
export const kubernetesDocumentationWeblinkName = "Kubernetes Documentation";
@@ -0,0 +1,35 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { docsUrl, slackUrl } from "../../../common/vars";
import type { WeblinkData } from "../../../common/weblinks-store/weblink-store";
import { getInjectable } from "@ogre-tools/injectable";
import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token";
import * as links from "../links";
const v514WeblinkStoreMigrationInjectable = getInjectable({
id: "v5.1.4-weblink-store-migration",
instantiate: () => ({
version: "5.1.4",
run(store) {
const weblinksRaw = store.get("weblinks");
const weblinks = (Array.isArray(weblinksRaw) ? weblinksRaw : []) as WeblinkData[];
weblinks.push(
{ id: "https://k8slens.dev", name: links.lensWebsiteLinkName, url: "https://k8slens.dev" },
{ id: docsUrl, name: links.lensDocumentationWeblinkName, url: docsUrl },
{ id: slackUrl, name: links.lensSlackWeblinkName, url: slackUrl },
{ id: "https://twitter.com/k8slens", name: links.lensTwitterWeblinkName, url: "https://twitter.com/k8slens" },
{ id: "https://medium.com/k8slens", name: links.lensBlogWeblinkName, url: "https://medium.com/k8slens" },
{ id: "https://kubernetes.io/docs/home/", name: links.kubernetesDocumentationWeblinkName, url: "https://kubernetes.io/docs/home/" },
);
store.set("weblinks", weblinks);
},
}),
injectionToken: weblinkStoreMigrationInjectionToken,
});
export default v514WeblinkStoreMigrationInjectable;
@@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { WeblinkData } from "../../../common/weblinks-store/weblink-store";
import * as links from "../links";
import { getInjectable } from "@ogre-tools/injectable";
import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token";
const v545Beta1WeblinkStoreMigrationInjectable = getInjectable({
id: "v5.4.5-beta.1-weblink-store-migration",
instantiate: () => ({
version: "5.4.5-beta.1 || >=5.5.0-alpha.0",
run(store) {
const weblinksRaw = store.get("weblinks");
const weblinks = (Array.isArray(weblinksRaw) ? weblinksRaw : []) as WeblinkData[];
const lensWebsiteLink = weblinks.find(weblink => weblink.name === links.lensWebsiteLinkName);
if (lensWebsiteLink) {
lensWebsiteLink.id = links.lensWebsiteWeblinkId;
}
const lensDocumentationWeblinkLink = weblinks.find(weblink => weblink.name === links.lensDocumentationWeblinkName);
if (lensDocumentationWeblinkLink) {
lensDocumentationWeblinkLink.id = links.lensDocumentationWeblinkId;
}
const lensSlackWeblinkLink = weblinks.find(weblink => weblink.name === links.lensSlackWeblinkName);
if (lensSlackWeblinkLink) {
lensSlackWeblinkLink.id = links.lensSlackWeblinkId;
}
const lensTwitterWeblinkLink = weblinks.find(weblink => weblink.name === links.lensTwitterWeblinkName);
if (lensTwitterWeblinkLink) {
lensTwitterWeblinkLink.id = links.lensTwitterWeblinkId;
}
const lensBlogWeblinkLink = weblinks.find(weblink => weblink.name === links.lensBlogWeblinkName);
if (lensBlogWeblinkLink) {
lensBlogWeblinkLink.id = links.lensBlogWeblinkId;
}
const kubernetesDocumentationWeblinkLink = weblinks.find(weblink => weblink.name === links.kubernetesDocumentationWeblinkName);
if (kubernetesDocumentationWeblinkLink) {
kubernetesDocumentationWeblinkLink.id = links.kubernetesDocumentationWeblinkId;
}
store.set("weblinks", weblinks);
},
}),
injectionToken: weblinkStoreMigrationInjectionToken,
});
export default v545Beta1WeblinkStoreMigrationInjectable;
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { docsUrl, slackUrl } from "../../../common/vars";
import type { WeblinkData } from "../../../common/weblinks-store/weblink-store";
import { getInjectable } from "@ogre-tools/injectable";
import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token";
import applicationInformationInjectable from "../../../common/vars/application-information.injectable";
import { lensDocumentationWeblinkId, lensSlackWeblinkId } from "../links";
const currentVersionWeblinkStoreMigrationInjectable = getInjectable({
id: "current-version-weblink-store-migration",
instantiate: (di) => {
const { version } = di.inject(applicationInformationInjectable);
return {
version, // Run always after upgrade
run(store) {
const weblinksRaw = store.get("weblinks");
const weblinks = (Array.isArray(weblinksRaw) ? weblinksRaw : []) as WeblinkData[];
const slackWeblink = weblinks.find(weblink => weblink.id === lensSlackWeblinkId);
if (slackWeblink) {
slackWeblink.url = slackUrl;
}
const docsWeblink = weblinks.find(weblink => weblink.id === lensDocumentationWeblinkId);
if (docsWeblink) {
docsWeblink.url = docsUrl;
}
store.set("weblinks", weblinks);
},
};
},
injectionToken: weblinkStoreMigrationInjectionToken,
});
export default currentVersionWeblinkStoreMigrationInjectable;