diff --git a/package.json b/package.json index ebe48438c4..1ad83a1e35 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.0.0-beta.12", + "version": "5.0.0-beta.13", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index baa7fc1409..b637bddac3 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -72,7 +72,7 @@ export interface ClusterModel { workspace?: string; /** User context in kubeconfig */ - contextName?: string; + contextName: string; /** Preferences */ preferences?: ClusterPreferences; diff --git a/src/migrations/cluster-store/5.0.0-beta.13.ts b/src/migrations/cluster-store/5.0.0-beta.13.ts new file mode 100644 index 0000000000..09628ed234 --- /dev/null +++ b/src/migrations/cluster-store/5.0.0-beta.13.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import type { ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../../common/cluster-store"; +import { MigrationDeclaration, migrationLog } from "../helpers"; +import { generateNewIdFor } from "../utils"; +import path from "path"; +import { app } from "electron"; +import { moveSync, removeSync } from "fs-extra"; + +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, right: Record): Record { + return { + ...right, + ...left + }; +} + +function mergeSet(left: Iterable, right: Iterable): string[] { + return [...new Set([...left, ...right])]; +} + +function mergeClusterModel(prev: ClusterModel, right: Omit): ClusterModel { + return { + id: prev.id, + kubeConfigPath: prev.id, + contextName: prev.contextName, + preferences: mergePreferences(prev.preferences ?? {}, right.preferences ?? {}), + metadata: prev.metadata, + labels: mergeLabels(prev.labels ?? {}, right.labels ?? {}), + accessibleNamespaces: mergeSet(prev.accessibleNamespaces ?? [], right.accessibleNamespaces ?? []), + }; +} + +function moveStorageFolder({ folder, newId, oldId }: { folder: string, newId: string, oldId: string }): void { + const oldPath = path.resolve(folder, `${oldId}.json`); + const newPath = path.resolve(folder, `${newId}.json`); + + try { + moveSync(oldPath, newPath); + } catch (error) { + if (String(error).includes("dest already exists")) { + migrationLog(`Multiple old lens-local-storage files for newId=${newId}. Removing ${oldId}.json`); + removeSync(oldPath); + } + } +} + +export default { + version: "5.0.0-beta.13", + run(store) { + const folder = path.resolve(app.getPath("userData"), "lens-local-storage"); + + const oldClusters: ClusterModel[] = store.get("clusters"); + const clusters = new Map(); + + for (const { id: oldId, ...cluster } of oldClusters) { + const newId = generateNewIdFor(cluster); + + if (clusters.has(newId)) { + clusters.set(newId, mergeClusterModel(clusters.get(newId), cluster)); + } else { + clusters.set(newId, { + ...cluster, + id: newId, + }); + moveStorageFolder({ folder, newId, oldId }); + } + } + + store.set("clusters", [...clusters.values()]); + } +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/index.ts b/src/migrations/cluster-store/index.ts index 9bf870825c..b68e098a16 100644 --- a/src/migrations/cluster-store/index.ts +++ b/src/migrations/cluster-store/index.ts @@ -31,6 +31,7 @@ import version270Beta0 from "./2.7.0-beta.0"; import version270Beta1 from "./2.7.0-beta.1"; 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( @@ -42,5 +43,6 @@ export default joinMigrations( version270Beta1, version360Beta1, version500Beta10, + version500Beta13, snap, ); diff --git a/src/migrations/hotbar-store/5.0.0-beta.10.ts b/src/migrations/hotbar-store/5.0.0-beta.10.ts index 1586f5c700..34c3aa34f9 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.10.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.10.ts @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { createHash } from "crypto"; import { app } from "electron"; import fse from "fs-extra"; import { isNull } from "lodash"; @@ -29,6 +28,7 @@ import type { ClusterStoreModel } from "../../common/cluster-store"; import { defaultHotbarCells, Hotbar, HotbarStore } from "../../common/hotbar-store"; import { catalogEntity } from "../../main/catalog-sources/general"; import type { MigrationDeclaration } from "../helpers"; +import { generateNewIdFor } from "../utils"; interface Pre500WorkspaceStoreModel { workspaces: { @@ -74,7 +74,7 @@ export default { if (workspaceHotbar?.items.length < defaultHotbarCells) { workspaceHotbar.items.push({ entity: { - uid: createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex"), + uid: generateNewIdFor(cluster), name: cluster.preferences.clusterName || cluster.contextName, } }); diff --git a/src/migrations/utils.ts b/src/migrations/utils.ts new file mode 100644 index 0000000000..f9f9985e38 --- /dev/null +++ b/src/migrations/utils.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { createHash } from "crypto"; + +export function generateNewIdFor(cluster: { kubeConfigPath: string, contextName: string }): string { + return createHash("md5").update(`${cluster.kubeConfigPath}:${cluster.contextName}`).digest("hex"); +}