1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

fix kubeconfig-sync issues

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2021-05-03 13:39:24 +03:00
parent d08aeaa2e4
commit 7494121a86
5 changed files with 39 additions and 57 deletions

View File

@ -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,
}));

View File

@ -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<string, [Cluster, CatalogEntity]>();
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<string, [Cluster, CatalogEntity]>();
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<string, [Cluster, CatalogEntity]>();
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<string, [Cluster, CatalogEntity]>();
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);

View File

@ -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<void> {
protected async startNewSync(filePath: string): Promise<void> {
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<string, RootSourceValue>;
// 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<CatalogEntity[]>, Disposer]> {
async function watchFileChanges(filePath: string): Promise<[IComputedValue<CatalogEntity[]>, 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)();

View File

@ -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<CatalogEntity>([]);
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<KubernetesCluster>("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;

View File

@ -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 {