mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce method to make store migrations injectable
- Use it for ClusterStore Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
3dce6f916e
commit
ad814ebdf6
@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import mockFs from "mock-fs";
|
||||
import type { BaseStoreDependencies } from "../base-store";
|
||||
import { BaseStore } from "../base-store";
|
||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||
import { BaseStore } from "../base-store/base-store";
|
||||
import { action, comparer, makeObservable, observable, toJS } from "mobx";
|
||||
import { readFileSync } from "fs";
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
|
||||
@ -5,19 +5,19 @@
|
||||
|
||||
import path from "path";
|
||||
import type Config from "conf";
|
||||
import type { Options as ConfOptions } from "conf/dist/source/types";
|
||||
import type { Migrations, Options as ConfOptions } from "conf/dist/source/types";
|
||||
import { ipcMain, ipcRenderer } from "electron";
|
||||
import type { IEqualsComparer } from "mobx";
|
||||
import { makeObservable, reaction, runInAction } from "mobx";
|
||||
import type { Disposer } from "./utils";
|
||||
import { isPromiseLike, toJS } from "./utils";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
||||
import type { Disposer } from "../utils";
|
||||
import { isPromiseLike, toJS } from "../utils";
|
||||
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "../ipc";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { kebabCase } from "lodash";
|
||||
import type { GetConfigurationFileModel } from "./get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import type { Logger } from "./logger";
|
||||
import type { GetConfigurationFileModel } from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import type { Logger } from "../logger";
|
||||
|
||||
export interface BaseStoreParams<T> extends ConfOptions<T> {
|
||||
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
|
||||
syncOptions?: {
|
||||
fireImmediately?: boolean;
|
||||
equals?: IEqualsComparer<T>;
|
||||
@ -29,6 +29,7 @@ export interface BaseStoreDependencies {
|
||||
readonly logger: Logger;
|
||||
readonly storeMigrationVersion: string;
|
||||
readonly directoryForUserData: string;
|
||||
readonly migrations: Migrations<Record<string, unknown>>;
|
||||
getConfigurationFileModel: GetConfigurationFileModel;
|
||||
}
|
||||
|
||||
@ -39,7 +40,7 @@ export abstract class BaseStore<T extends object> {
|
||||
protected storeConfig?: Config<T>;
|
||||
protected syncDisposers: Disposer[] = [];
|
||||
|
||||
readonly displayName: string;
|
||||
readonly displayName = this.params.configName;
|
||||
|
||||
protected constructor(
|
||||
protected readonly dependencies: BaseStoreDependencies,
|
||||
@ -48,10 +49,6 @@ export abstract class BaseStore<T extends object> {
|
||||
makeObservable(this);
|
||||
|
||||
this.displayName = this.params.configName;
|
||||
|
||||
if (ipcRenderer) {
|
||||
this.params.migrations = undefined; // don't run migrations on renderer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,6 +61,7 @@ export abstract class BaseStore<T extends object> {
|
||||
projectVersion: this.dependencies.storeMigrationVersion,
|
||||
cwd: this.cwd(),
|
||||
...this.params,
|
||||
migrations: this.dependencies.migrations as Migrations<T>,
|
||||
});
|
||||
|
||||
const res = this.fromStore(this.storeConfig.store);
|
||||
46
src/common/base-store/migrations.injectable.ts
Normal file
46
src/common/base-store/migrations.injectable.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { InjectionToken } from "@ogre-tools/injectable";
|
||||
import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable";
|
||||
import type Conf from "conf/dist/source";
|
||||
import type { Migrations } from "conf/dist/source/types";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import { getOrInsert, iter } from "../utils";
|
||||
|
||||
export interface MigrationDeclaration {
|
||||
version: string;
|
||||
run(store: Conf<Partial<Record<string, unknown>>>): void;
|
||||
}
|
||||
|
||||
const storeMigrationsInjectable = getInjectable({
|
||||
id: "store-migrations",
|
||||
instantiate: (di, token): Migrations<Record<string, unknown>> => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const declarations = di.injectMany(token);
|
||||
const migrations = new Map<string, MigrationDeclaration["run"][]>();
|
||||
|
||||
for (const decl of declarations) {
|
||||
getOrInsert(migrations, decl.version, []).push(decl.run);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
iter.map(
|
||||
migrations,
|
||||
([v, fns]) => [v, (store) => {
|
||||
logger.info(`Running ${v} migration for ${store.path}`);
|
||||
|
||||
for (const fn of fns) {
|
||||
fn(store);
|
||||
}
|
||||
}],
|
||||
),
|
||||
);
|
||||
},
|
||||
lifecycle: lifecycleEnum.keyedSingleton({
|
||||
getInstanceKey: (di, token: InjectionToken<MigrationDeclaration, void>) => token.id,
|
||||
}),
|
||||
});
|
||||
|
||||
export default storeMigrationsInjectable;
|
||||
@ -11,6 +11,8 @@ import directoryForUserDataInjectable from "../app-paths/directory-for-user-data
|
||||
import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable";
|
||||
import storeMigrationsInjectable from "../base-store/migrations.injectable";
|
||||
import { clusterStoreMigrationInjectionToken } from "./migration-token";
|
||||
|
||||
const clusterStoreInjectable = getInjectable({
|
||||
id: "cluster-store",
|
||||
@ -23,6 +25,7 @@ const clusterStoreInjectable = getInjectable({
|
||||
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||
migrations: di.inject(storeMigrationsInjectable, clusterStoreMigrationInjectionToken),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -6,10 +6,9 @@
|
||||
|
||||
import { ipcMain, ipcRenderer, webFrame } from "electron";
|
||||
import { action, comparer, computed, makeObservable, observable, reaction } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../base-store";
|
||||
import { BaseStore } from "../base-store";
|
||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||
import { BaseStore } from "../base-store/base-store";
|
||||
import { Cluster } from "../cluster/cluster";
|
||||
import migrations from "../../migrations/cluster-store";
|
||||
import { disposer, toJS } from "../utils";
|
||||
import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types";
|
||||
import { requestInitialClusterStates } from "../../renderer/ipc";
|
||||
@ -31,7 +30,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
readonly displayName = "ClusterStore";
|
||||
readonly clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
protected disposer = disposer();
|
||||
protected readonly disposer = disposer();
|
||||
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
@ -40,7 +39,6 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
syncOptions: {
|
||||
equals: comparer.structural,
|
||||
},
|
||||
migrations,
|
||||
});
|
||||
|
||||
makeObservable(this);
|
||||
|
||||
11
src/common/cluster-store/migration-token.ts
Normal file
11
src/common/cluster-store/migration-token.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MigrationDeclaration } from "../../migrations/helpers";
|
||||
|
||||
export const clusterStoreMigrationInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||
id: "cluster-store-migration",
|
||||
});
|
||||
19
src/common/fs/read-file-buffer-sync.injectable.ts
Normal file
19
src/common/fs/read-file-buffer-sync.injectable.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type ReadFileBufferSync = (filePath: string) => Buffer;
|
||||
|
||||
const readFileBufferSyncInjectable = getInjectable({
|
||||
id: "read-file-buffer-sync",
|
||||
instantiate: (di): ReadFileBufferSync => {
|
||||
const { readFileSync } = di.inject(fsInjectable);
|
||||
|
||||
return (filePath) => readFileSync(filePath);
|
||||
},
|
||||
});
|
||||
|
||||
export default readFileBufferSyncInjectable;
|
||||
13
src/common/fs/read-json-sync.injectable.ts
Normal file
13
src/common/fs/read-json-sync.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
const readJsonSyncInjectable = getInjectable({
|
||||
id: "read-json-sync",
|
||||
instantiate: (di) => di.inject(fsInjectable).readJsonSync,
|
||||
});
|
||||
|
||||
export default readJsonSyncInjectable;
|
||||
@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { action, comparer, observable, makeObservable, computed } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../base-store";
|
||||
import { BaseStore } from "../base-store";
|
||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||
import { BaseStore } from "../base-store/base-store";
|
||||
import migrations from "../../migrations/hotbar-store";
|
||||
import { toJS } from "../utils";
|
||||
import type { CatalogEntity } from "../catalog";
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
|
||||
import { app } from "electron";
|
||||
import { action, observable, reaction, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../base-store";
|
||||
import { BaseStore } from "../base-store";
|
||||
import type { BaseStoreDependencies } from "../base-store/base-store";
|
||||
import { BaseStore } from "../base-store/base-store";
|
||||
import migrations from "../../migrations/user-store";
|
||||
import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils";
|
||||
import { DESCRIPTORS } from "./preferences-helpers";
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { action, comparer, observable, makeObservable } from "mobx";
|
||||
import type { BaseStoreDependencies } from "./base-store";
|
||||
import { BaseStore } from "./base-store";
|
||||
import type { BaseStoreDependencies } from "./base-store/base-store";
|
||||
import { BaseStore } from "./base-store/base-store";
|
||||
import migrations from "../migrations/weblinks-store";
|
||||
import * as uuid from "uuid";
|
||||
import { toJS } from "./utils";
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
|
||||
import { SHA256 } from "crypto-js";
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../../../common/base-store";
|
||||
import { BaseStore } from "../../../common/base-store";
|
||||
import type { BaseStoreDependencies } from "../../../common/base-store/base-store";
|
||||
import { BaseStore } from "../../../common/base-store/base-store";
|
||||
import type { LensExtensionId } from "../../lens-extension";
|
||||
import { getOrInsertWithAsync, toJS } from "../../../common/utils";
|
||||
import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable";
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { BaseStoreParams } from "../common/base-store";
|
||||
import { BaseStore } from "../common/base-store";
|
||||
import type { BaseStoreParams } from "../common/base-store/base-store";
|
||||
import { BaseStore } from "../common/base-store/base-store";
|
||||
import * as path from "path";
|
||||
import type { LensExtension } from "./lens-extension";
|
||||
import assert from "assert";
|
||||
@ -15,6 +15,11 @@ import directoryForUserDataInjectable from "../common/app-paths/directory-for-us
|
||||
import getConfigurationFileModelInjectable from "../common/get-configuration-file-model/get-configuration-file-model.injectable";
|
||||
import loggerInjectable from "../common/logger.injectable";
|
||||
import storeMigrationVersionInjectable from "../common/vars/store-migration-version.injectable";
|
||||
import type { Migrations } from "conf/dist/source/types";
|
||||
|
||||
export interface ExtensionStoreParams<T extends object> extends BaseStoreParams<T> {
|
||||
migrations?: Migrations<T>;
|
||||
}
|
||||
|
||||
export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||
private static readonly instances = new WeakMap<object, ExtensionStore<object>>();
|
||||
@ -39,7 +44,7 @@ export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||
return ExtensionStore.instances.get(this) as (T | undefined);
|
||||
}
|
||||
|
||||
constructor(params: BaseStoreParams<T>) {
|
||||
constructor({ migrations, ...params }: ExtensionStoreParams<T>) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
super({
|
||||
@ -47,6 +52,7 @@ export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||
getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
|
||||
migrations: migrations as Migrations<Record<string, unknown>>,
|
||||
}, params);
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
import type { LensExtensionId } from "../lens-extension";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import { toJS } from "../../common/utils";
|
||||
import type { BaseStoreDependencies } from "../../common/base-store";
|
||||
import { BaseStore } from "../../common/base-store";
|
||||
import type { BaseStoreDependencies } from "../../common/base-store/base-store";
|
||||
import { BaseStore } from "../../common/base-store/base-store";
|
||||
|
||||
export interface LensExtensionsStoreModel {
|
||||
extensions: Record<LensExtensionId, LensExtensionState>;
|
||||
|
||||
103
src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts
Normal file
103
src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import 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";
|
||||
import { migrationLog } from "../../../migrations/helpers";
|
||||
|
||||
interface Pre360ClusterModel extends ClusterModel {
|
||||
kubeConfig?: string;
|
||||
}
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
|
||||
import fsInjectable from "../../../common/fs/fs.injectable";
|
||||
import readFileBufferSyncInjectable from "../../../common/fs/read-file-buffer-sync.injectable";
|
||||
|
||||
const v360Beta1ClusterStoreMigrationInjectable = getInjectable({
|
||||
id: "v3.6.0-beta.1-cluster-store-migration",
|
||||
instantiate: (di) => {
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
|
||||
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigDirectoryInjectable);
|
||||
const readFileSync = di.inject(readFileSyncInjectable);
|
||||
const readFileBufferSync = di.inject(readFileBufferSyncInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const {
|
||||
ensureDirSync,
|
||||
writeFileSync,
|
||||
} = di.inject(fsInjectable);
|
||||
|
||||
return {
|
||||
version: "3.6.0-beta.1",
|
||||
run: (store) => {
|
||||
const storedClusters = (store.get("clusters") ?? []) as Pre360ClusterModel[];
|
||||
const migratedClusters: ClusterModel[] = [];
|
||||
|
||||
ensureDirSync(kubeConfigsPath);
|
||||
|
||||
migrationLog("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, { encoding: "utf-8", mode: 0o600 });
|
||||
|
||||
clusterModel.kubeConfigPath = absPath;
|
||||
clusterModel.contextName = loadConfigFromString(readFileSync(absPath)).config.getCurrentContext();
|
||||
delete clusterModel.kubeConfig;
|
||||
|
||||
} catch (error) {
|
||||
migrationLog(`Failed to migrate Kubeconfig for cluster "${clusterModel.id}", removing clusterModel...`, error);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* migrate cluster icon
|
||||
*/
|
||||
try {
|
||||
if (clusterModel.preferences?.icon) {
|
||||
migrationLog(`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) {
|
||||
migrationLog(`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;
|
||||
@ -3,15 +3,12 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../../common/cluster-types";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import { generateNewIdFor } from "../utils";
|
||||
import type { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../../../common/cluster-types";
|
||||
import { moveSync, removeSync } from "fs-extra";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isDefined } from "../../common/utils";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isDefined } from "../../../common/utils";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import { generateNewIdFor } from "../../../migrations/utils";
|
||||
|
||||
function mergePrometheusPreferences(left: ClusterPrometheusPreferences, right: ClusterPrometheusPreferences): ClusterPrometheusPreferences {
|
||||
if (left.prometheus && left.prometheusProvider) {
|
||||
@ -78,13 +75,16 @@ function mergeClusterModel(prev: ClusterModel, right: Omit<ClusterModel, "id">):
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
version: "5.0.0-beta.13",
|
||||
run(store) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token";
|
||||
|
||||
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`);
|
||||
@ -94,34 +94,42 @@ export default {
|
||||
moveSync(oldPath, newPath);
|
||||
} catch (error) {
|
||||
if (String(error).includes("dest already exists")) {
|
||||
migrationLog(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`);
|
||||
logger.info(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`);
|
||||
removeSync(oldPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const folder = joinPaths(userDataPath, "lens-local-storage");
|
||||
const oldClusters: ClusterModel[] = store.get("clusters") ?? [];
|
||||
const clusters = new Map<string, ClusterModel>();
|
||||
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);
|
||||
for (const { id: oldId, ...cluster } of oldClusters) {
|
||||
const newId = generateNewIdFor(cluster);
|
||||
const newCluster = clusters.get(newId);
|
||||
|
||||
if (newCluster) {
|
||||
migrationLog(`Duplicate entries for ${newId}`, { oldId });
|
||||
clusters.set(newId, mergeClusterModel(newCluster, cluster));
|
||||
} else {
|
||||
migrationLog(`First entry for ${newId}`, { oldId });
|
||||
clusters.set(newId, {
|
||||
...cluster,
|
||||
id: newId,
|
||||
workspaces: [cluster.workspace].filter(isDefined),
|
||||
});
|
||||
moveStorageFolder({ folder, newId, oldId });
|
||||
}
|
||||
}
|
||||
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()]);
|
||||
store.set("clusters", [...clusters.values()]);
|
||||
},
|
||||
};
|
||||
},
|
||||
} as MigrationDeclaration;
|
||||
injectionToken: clusterStoreMigrationInjectionToken,
|
||||
});
|
||||
|
||||
export default v500Beta13ClusterStoreMigrationInjectable;
|
||||
60
src/main/cluster/store-migrations/snap.injectable.ts
Normal file
60
src/main/cluster/store-migrations/snap.injectable.ts
Normal file
@ -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 fsInjectable from "../../../common/fs/fs.injectable";
|
||||
import type { ClusterModel } from "../../../common/cluster-types";
|
||||
|
||||
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 { existsSync } = di.inject(fsInjectable);
|
||||
|
||||
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 (!existsSync(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;
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
/**
|
||||
* 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 fse from "fs-extra";
|
||||
import { loadConfigFromString } from "../../common/kube-helpers";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import type { ClusterModel } from "../../common/cluster-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 directoryForKubeConfigsInjectable
|
||||
from "../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import getCustomKubeConfigDirectoryInjectable
|
||||
from "../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
|
||||
import readFileSyncInjectable from "../../common/fs/read-file-sync.injectable";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface Pre360ClusterModel extends ClusterModel {
|
||||
kubeConfig?: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
version: "3.6.0-beta.1",
|
||||
run(store) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable);
|
||||
const getCustomKubeConfigDirectory = di.inject(getCustomKubeConfigDirectoryInjectable);
|
||||
const readFileSync = di.inject(readFileSyncInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
const storedClusters: Pre360ClusterModel[] = store.get("clusters") ?? [];
|
||||
const migratedClusters: ClusterModel[] = [];
|
||||
|
||||
fse.ensureDirSync(kubeConfigsPath);
|
||||
|
||||
migrationLog("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
|
||||
fse.writeFileSync(absPath, clusterModel.kubeConfig, { encoding: "utf-8", mode: 0o600 });
|
||||
|
||||
clusterModel.kubeConfigPath = absPath;
|
||||
clusterModel.contextName = loadConfigFromString(readFileSync(absPath)).config.getCurrentContext();
|
||||
delete clusterModel.kubeConfig;
|
||||
|
||||
} catch (error) {
|
||||
migrationLog(`Failed to migrate Kubeconfig for cluster "${clusterModel.id}", removing clusterModel...`, error);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* migrate cluster icon
|
||||
*/
|
||||
try {
|
||||
if (clusterModel.preferences?.icon) {
|
||||
migrationLog(`migrating ${clusterModel.preferences.icon} for ${clusterModel.preferences.clusterName}`);
|
||||
const iconPath = clusterModel.preferences.icon.replace("store://", "");
|
||||
const fileData = fse.readFileSync(joinPaths(userDataPath, iconPath));
|
||||
|
||||
clusterModel.preferences.icon = `data:;base64,${fileData.toString("base64")}`;
|
||||
} else {
|
||||
delete clusterModel.preferences?.icon;
|
||||
}
|
||||
} catch (error) {
|
||||
migrationLog(`Failed to migrate cluster icon for cluster "${clusterModel.id}"`, error);
|
||||
delete clusterModel.preferences?.icon;
|
||||
}
|
||||
|
||||
migratedClusters.push(clusterModel);
|
||||
}
|
||||
|
||||
store.set("clusters", migratedClusters);
|
||||
},
|
||||
} as MigrationDeclaration;
|
||||
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import fse from "fs-extra";
|
||||
import type { ClusterModel } from "../../common/cluster-types";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { isErrnoException } from "../../common/utils";
|
||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
||||
|
||||
interface Pre500WorkspaceStoreModel {
|
||||
workspaces: {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export default {
|
||||
version: "5.0.0-beta.10",
|
||||
run(store) {
|
||||
const di = getLegacyGlobalDiForExtensionApi();
|
||||
|
||||
const userDataPath = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
try {
|
||||
const workspaceData: Pre500WorkspaceStoreModel = fse.readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json"));
|
||||
const workspaces = new Map<string, string>(); // mapping from WorkspaceId to name
|
||||
|
||||
for (const { id, name } of workspaceData.workspaces) {
|
||||
workspaces.set(id, name);
|
||||
}
|
||||
|
||||
const clusters: ClusterModel[] = store.get("clusters") ?? [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
} as MigrationDeclaration;
|
||||
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
// Cluster store migrations
|
||||
|
||||
import { joinMigrations } from "../helpers";
|
||||
|
||||
import version360Beta1 from "./3.6.0-beta.1";
|
||||
import version500Beta10 from "./5.0.0-beta.10";
|
||||
import version500Beta13 from "./5.0.0-beta.13";
|
||||
import snap from "./snap";
|
||||
|
||||
export default joinMigrations(
|
||||
version360Beta1,
|
||||
version500Beta10,
|
||||
version500Beta13,
|
||||
snap,
|
||||
);
|
||||
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* 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 type { ClusterModel } from "../../common/cluster-types";
|
||||
import fs from "fs";
|
||||
import type { MigrationDeclaration } from "../helpers";
|
||||
import { migrationLog } from "../helpers";
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
export default {
|
||||
// TODO: replace with injection once migrations are made as injectables
|
||||
version: packageJson.version, // Run always after upgrade
|
||||
run(store) {
|
||||
if (!process.env["SNAP"]) return;
|
||||
|
||||
migrationLog("Migrating embedded kubeconfig paths");
|
||||
const storedClusters: ClusterModel[] = store.get("clusters") || [];
|
||||
|
||||
if (!storedClusters.length) return;
|
||||
|
||||
migrationLog("Number of clusters to migrate: ", storedClusters.length);
|
||||
const migratedClusters = storedClusters
|
||||
.map(cluster => {
|
||||
/**
|
||||
* replace snap version with 'current' in kubeconfig path
|
||||
*/
|
||||
if (!fs.existsSync(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);
|
||||
},
|
||||
} as MigrationDeclaration;
|
||||
@ -16,7 +16,7 @@ export function migrationLog(...args: any[]) {
|
||||
|
||||
export interface MigrationDeclaration {
|
||||
version: string;
|
||||
run(store: Conf<any>): void;
|
||||
run(store: Conf<Partial<Record<string, unknown>>>): void;
|
||||
}
|
||||
|
||||
export function joinMigrations(...declarations: MigrationDeclaration[]): Migrations<any> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user