From 7494121a86ddc9689a87a993a4834b52985009da Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 3 May 2021 13:39:24 +0300 Subject: [PATCH] fix kubeconfig-sync issues Signed-off-by: Jari Kolehmainen --- src/main/catalog-pusher.ts | 4 +- .../__test__/kubeconfig-sync.test.ts | 18 ++++---- src/main/catalog-sources/kubeconfig-sync.ts | 31 +++++++------- src/main/cluster-manager.ts | 41 ++++++------------- src/main/index.ts | 2 +- 5 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/main/catalog-pusher.ts b/src/main/catalog-pusher.ts index 8bc028cbd7..532faf37e3 100644 --- a/src/main/catalog-pusher.ts +++ b/src/main/catalog-pusher.ts @@ -14,8 +14,8 @@ export class CatalogPusher { init() { const disposers: Disposer[] = []; - disposers.push(reaction(() => this.catalog.items, (items) => { - broadcastMessage("catalog:items", toJS(items, { recurseEverything: true })); + disposers.push(reaction(() => toJS(this.catalog.items, { recurseEverything: true }), (items) => { + broadcastMessage("catalog:items", items); }, { fireImmediately: true, })); diff --git a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index beda3ca890..63a17a648f 100644 --- a/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -5,10 +5,12 @@ import { Cluster } from "../../cluster"; import { computeDiff, configToModels } from "../kubeconfig-sync"; import mockFs from "mock-fs"; import fs from "fs"; +import { ClusterStore } from "../../../common/cluster-store"; describe("kubeconfig-sync.source tests", () => { beforeEach(() => { mockFs(); + ClusterStore.createInstance(); }); afterEach(() => { @@ -57,10 +59,9 @@ describe("kubeconfig-sync.source tests", () => { it("should leave an empty source empty if there are no entries", () => { const contents = ""; const rootSource = new ObservableMap(); - const port = 0; const filePath = "/bar"; - computeDiff(contents, rootSource, port, filePath); + computeDiff(contents, rootSource, filePath); expect(rootSource.size).toBe(0); }); @@ -93,12 +94,11 @@ describe("kubeconfig-sync.source tests", () => { currentContext: "foobar" }); const rootSource = new ObservableMap(); - const port = 0; const filePath = "/bar"; fs.writeFileSync(filePath, contents); - computeDiff(contents, rootSource, port, filePath); + computeDiff(contents, rootSource, filePath); expect(rootSource.size).toBe(1); @@ -137,12 +137,11 @@ describe("kubeconfig-sync.source tests", () => { currentContext: "foobar" }); const rootSource = new ObservableMap(); - const port = 0; const filePath = "/bar"; fs.writeFileSync(filePath, contents); - computeDiff(contents, rootSource, port, filePath); + computeDiff(contents, rootSource, filePath); expect(rootSource.size).toBe(1); @@ -151,7 +150,7 @@ describe("kubeconfig-sync.source tests", () => { expect(c.kubeConfigPath).toBe("/bar"); expect(c.contextName).toBe("context-name"); - computeDiff("{}", rootSource, port, filePath); + computeDiff("{}", rootSource, filePath); expect(rootSource.size).toBe(0); }); @@ -192,12 +191,11 @@ describe("kubeconfig-sync.source tests", () => { currentContext: "foobar" }); const rootSource = new ObservableMap(); - const port = 0; const filePath = "/bar"; fs.writeFileSync(filePath, contents); - computeDiff(contents, rootSource, port, filePath); + computeDiff(contents, rootSource, filePath); expect(rootSource.size).toBe(2); @@ -237,7 +235,7 @@ describe("kubeconfig-sync.source tests", () => { currentContext: "foobar" }); - computeDiff(newContents, rootSource, port, filePath); + computeDiff(newContents, rootSource, filePath); expect(rootSource.size).toBe(1); diff --git a/src/main/catalog-sources/kubeconfig-sync.ts b/src/main/catalog-sources/kubeconfig-sync.ts index 9cf2bf57d4..f90fb85caf 100644 --- a/src/main/catalog-sources/kubeconfig-sync.ts +++ b/src/main/catalog-sources/kubeconfig-sync.ts @@ -3,7 +3,6 @@ import { CatalogEntity, catalogEntityRegistry } from "../../common/catalog"; import { watch } from "chokidar"; import fs from "fs"; import fse from "fs-extra"; -import * as uuid from "uuid"; import stream from "stream"; import { Disposer, ExtendedObservableMap, iter, Singleton } from "../../common/utils"; import logger from "../logger"; @@ -13,6 +12,7 @@ import { Cluster } from "../cluster"; import { catalogEntityFromCluster } from "../cluster-manager"; import { UserStore } from "../../common/user-store"; import { ClusterStore, UpdateClusterModel } from "../../common/cluster-store"; +import { createHash } from "crypto"; const logPrefix = "[KUBECONFIG-SYNC]:"; @@ -24,7 +24,7 @@ export class KubeconfigSyncManager extends Singleton { protected static readonly syncName = "lens:kube-sync"; @action - startSync(port: number): void { + startSync(): void { if (this.syncing) { return; } @@ -41,16 +41,16 @@ export class KubeconfigSyncManager extends Singleton { ))); // This must be done so that c&p-ed clusters are visible - this.startNewSync(ClusterStore.storedKubeConfigFolder, port); + this.startNewSync(ClusterStore.storedKubeConfigFolder); for (const filePath of UserStore.getInstance().syncKubeconfigEntries.keys()) { - this.startNewSync(filePath, port); + this.startNewSync(filePath); } this.syncListDisposer = UserStore.getInstance().syncKubeconfigEntries.observe(change => { switch (change.type) { case "add": - this.startNewSync(change.name, port); + this.startNewSync(change.name); break; case "delete": this.stopOldSync(change.name); @@ -72,14 +72,14 @@ export class KubeconfigSyncManager extends Singleton { } @action - protected async startNewSync(filePath: string, port: number): Promise { + protected async startNewSync(filePath: string): Promise { if (this.sources.has(filePath)) { // don't start a new sync if we already have one return void logger.debug(`${logPrefix} already syncing file/folder`, { filePath }); } try { - this.sources.set(filePath, await watchFileChanges(filePath, port)); + this.sources.set(filePath, await watchFileChanges(filePath)); logger.info(`${logPrefix} starting sync of file/folder`, { filePath }); logger.debug(`${logPrefix} ${this.sources.size} files/folders watched`, { files: Array.from(this.sources.keys()) }); @@ -124,7 +124,7 @@ type RootSourceValue = [Cluster, CatalogEntity]; type RootSource = ObservableMap; // exported for testing -export function computeDiff(contents: string, source: RootSource, port: number, filePath: string): void { +export function computeDiff(contents: string, source: RootSource, filePath: string): void { runInAction(() => { try { const rawModels = configToModels(loadConfigFromString(contents), filePath); @@ -156,14 +156,13 @@ export function computeDiff(contents: string, source: RootSource, port: number, for (const [contextName, model] of models) { // add new clusters to the source try { - const cluster = new Cluster({ ...model, id: uuid.v4() }); + const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex"); + const cluster = ClusterStore.getInstance().getById(clusterId) || new Cluster({ ...model, id: clusterId}); if (!cluster.apiUrl) { throw new Error("Cluster constructor failed, see above error"); } - cluster.init(port); - const entity = catalogEntityFromCluster(cluster); entity.metadata.labels.file = filePath; @@ -181,7 +180,7 @@ export function computeDiff(contents: string, source: RootSource, port: number, }); } -function diffChangedConfig(filePath: string, source: RootSource, port: number): Disposer { +function diffChangedConfig(filePath: string, source: RootSource): Disposer { logger.debug(`${logPrefix} file changed`, { filePath }); // TODO: replace with an AbortController with fs.readFile when we upgrade to Node 16 (after it comes out) @@ -214,14 +213,14 @@ function diffChangedConfig(filePath: string, source: RootSource, port: number): }) .on("end", () => { if (!closed) { - computeDiff(Buffer.concat(bufs).toString("utf-8"), source, port, filePath); + computeDiff(Buffer.concat(bufs).toString("utf-8"), source, filePath); } }); return cleanup; } -async function watchFileChanges(filePath: string, port: number): Promise<[IComputedValue, Disposer]> { +async function watchFileChanges(filePath: string): Promise<[IComputedValue, Disposer]> { const stat = await fse.stat(filePath); // traverses symlinks, is a race condition const watcher = watch(filePath, { followSymlinks: true, @@ -235,10 +234,10 @@ async function watchFileChanges(filePath: string, port: number): Promise<[ICompu watcher .on("change", (childFilePath) => { stoppers.get(childFilePath)(); - stoppers.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrDefault(childFilePath), port)); + stoppers.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrDefault(childFilePath))); }) .on("add", (childFilePath) => { - stoppers.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrDefault(childFilePath), port)); + stoppers.set(childFilePath, diffChangedConfig(childFilePath, rootSource.getOrDefault(childFilePath))); }) .on("unlink", (childFilePath) => { stoppers.get(childFilePath)(); diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index 369c4f61b9..bfa801c25c 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -1,24 +1,21 @@ import "../common/cluster-ipc"; import type http from "http"; import { ipcMain } from "electron"; -import { action, autorun, observable, reaction, toJS } from "mobx"; +import { action, autorun, reaction, toJS } from "mobx"; import { ClusterStore, getClusterIdFromHost } from "../common/cluster-store"; import { Cluster } from "./cluster"; import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; import { Singleton } from "../common/utils"; -import { CatalogEntity, catalogEntityRegistry } from "../common/catalog"; +import { catalogEntityRegistry } from "../common/catalog"; import { KubernetesCluster } from "../common/catalog-entities/kubernetes-cluster"; const clusterOwnerRef = "ClusterManager"; export class ClusterManager extends Singleton { - catalogSource = observable.array([]); - constructor(public readonly port: number) { super(); - catalogEntityRegistry.addObservableSource("lens:kubernetes-clusters", this.catalogSource); // auto-init clusters reaction(() => ClusterStore.getInstance().enabledClustersList, (clusters) => { clusters.forEach((cluster) => { @@ -31,7 +28,7 @@ export class ClusterManager extends Singleton { }, { fireImmediately: true }); reaction(() => toJS(ClusterStore.getInstance().enabledClustersList, { recurseEverything: true }), () => { - this.updateCatalogSource(ClusterStore.getInstance().enabledClustersList); + this.updateCatalog(ClusterStore.getInstance().enabledClustersList); }, { fireImmediately: true }); reaction(() => catalogEntityRegistry.getItemsForApiKind("entity.k8slens.dev/v1alpha1", "KubernetesCluster"), (entities) => { @@ -58,31 +55,20 @@ export class ClusterManager extends Singleton { ipcMain.on("network:online", () => { this.onNetworkOnline(); }); } - @action protected updateCatalogSource(clusters: Cluster[]) { - this.catalogSource.replace(this.catalogSource.filter(entity => ( - clusters.find((cluster) => entity.metadata.uid === cluster.id) - ))); - + @action protected updateCatalog(clusters: Cluster[]) { for (const cluster of clusters) { - if (cluster.ownerRef) { - continue; - } + const index = catalogEntityRegistry.items.findIndex((entity) => entity.metadata.uid === cluster.id); - const entityIndex = this.catalogSource.findIndex((entity) => entity.metadata.uid === cluster.id); - const newEntity = catalogEntityFromCluster(cluster); + if (index !== -1) { + const entity = catalogEntityRegistry.items[index]; - if (entityIndex === -1) { - this.catalogSource.push(newEntity); - } else { - const oldEntity = this.catalogSource[entityIndex]; + entity.status.phase = cluster.disconnected ? "disconnected" : "connected"; + entity.status.active = !cluster.disconnected; - newEntity.status.phase = cluster.disconnected ? "disconnected" : "connected"; - newEntity.status.active = !cluster.disconnected; - newEntity.metadata.labels = { - ...newEntity.metadata.labels, - ...oldEntity.metadata.labels - }; - this.catalogSource.splice(entityIndex, 1, newEntity); + if (cluster.preferences?.clusterName) { + entity.metadata.name = cluster.preferences.clusterName; + } + catalogEntityRegistry.items.splice(index, 1, entity); } } } @@ -109,7 +95,6 @@ export class ClusterManager extends Singleton { } else { cluster.enabled = true; cluster.ownerRef ||= clusterOwnerRef; - cluster.preferences.clusterName = entity.metadata.name; cluster.kubeConfigPath = entity.spec.kubeconfigPath; cluster.contextName = entity.spec.kubeconfigContext; diff --git a/src/main/index.ts b/src/main/index.ts index 2b4a7ae911..35d6e3a841 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -133,7 +133,7 @@ app.on("ready", async () => { const clusterManager = ClusterManager.getInstance(); // create kubeconfig sync manager - KubeconfigSyncManager.createInstance().startSync(clusterManager.port); + KubeconfigSyncManager.createInstance().startSync(); // run proxy try {