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:
parent
d08aeaa2e4
commit
7494121a86
@ -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,
|
||||
}));
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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)();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user