Fix cluster frame display issue (#5518)

* Fix cluster frame display issue

- Add some defensive code to prevent this sort of infinite loop

- Add some unit tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix unit tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix build

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix lint

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Factor out injectable for getting of cluster config data

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-06-20 22:23:15 -04:00
1 parent 8c31e56462
commit de43df1cca
40 files changed
+2126 -815

No files matched your search

@@ -14,7 +14,6 @@ import clusterStoreInjectable from "../../../common/cluster-store/cluster-store.
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import { ClusterStore } from "../../../common/cluster-store/cluster-store";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import appVersionInjectable from "../../../common/get-configuration-file-model/app-version/app-version.injectable";
import clusterManagerInjectable from "../../cluster-manager.injectable";
@@ -23,6 +22,8 @@ import directoryForTempInjectable from "../../../common/app-paths/directory-for-
import kubectlBinaryNameInjectable from "../../kubectl/binary-name.injectable";
import kubectlDownloadingNormalizedArchInjectable from "../../kubectl/normalized-arch.injectable";
import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable";
import { iter } from "../../../common/utils";
import fsInjectable from "../../../common/fs/fs.injectable";
jest.mock("electron", () => ({
app: {
@@ -54,10 +55,9 @@ describe("kubeconfig-sync.source tests", () => {
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin");
di.override(clusterStoreInjectable, () =>
ClusterStore.createInstance({ createCluster: () => null as never }),
);
di.permitSideEffects(fsInjectable);
di.unoverride(clusterStoreInjectable);
di.permitSideEffects(clusterStoreInjectable);
di.permitSideEffects(getConfigurationFileModelInjectable);
di.permitSideEffects(appVersionInjectable);
@@ -66,13 +66,10 @@ describe("kubeconfig-sync.source tests", () => {
createCluster: di.inject(createClusterInjectionToken),
clusterManager: di.inject(clusterManagerInjectable),
});
di.inject(clusterStoreInjectable);
});
afterEach(() => {
mockFs.restore();
ClusterStore.resetInstance();
});
describe("configsToModels", () => {
@@ -108,8 +105,8 @@ describe("kubeconfig-sync.source tests", () => {
const models = configToModels(config, "/bar");
expect(models.length).toBe(1);
expect(models[0].contextName).toBe("context-name");
expect(models[0].kubeConfigPath).toBe("/bar");
expect(models[0][0].contextName).toBe("context-name");
expect(models[0][0].kubeConfigPath).toBe("/bar");
});
});
@@ -160,7 +157,8 @@ describe("kubeconfig-sync.source tests", () => {
expect(rootSource.size).toBe(1);
const c = rootSource.values().next().value[0] as Cluster;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const c = (iter.first(rootSource.values())!)[0];
expect(c.kubeConfigPath).toBe("/bar");
expect(c.contextName).toBe("context-name");
@@ -25,9 +25,10 @@ import { createHash } from "crypto";
import { homedir } from "os";
import globToRegExp from "glob-to-regexp";
import { inspect } from "util";
import type { ClusterModel, UpdateClusterModel } from "../../../common/cluster-types";
import type { ClusterConfigData, UpdateClusterModel } from "../../../common/cluster-types";
import type { Cluster } from "../../../common/cluster/cluster";
import type { CatalogEntityRegistry } from "../../catalog/entity-registry";
import type { CreateCluster } from "../../../common/cluster/create-cluster-injection-token";
const logPrefix = "[KUBECONFIG-SYNC]:";
@@ -56,7 +57,7 @@ interface KubeconfigSyncManagerDependencies {
readonly directoryForKubeConfigs: string;
readonly entityRegistry: CatalogEntityRegistry;
readonly clusterManager: ClusterManager;
createCluster: (model: ClusterModel) => Cluster;
createCluster: CreateCluster;
}
const kubeConfigSyncName = "lens:kube-sync";
@@ -147,17 +148,22 @@ export class KubeconfigSyncManager {
}
// exported for testing
export function configToModels(rootConfig: KubeConfig, filePath: string): UpdateClusterModel[] {
const validConfigs = [];
export function configToModels(rootConfig: KubeConfig, filePath: string): [UpdateClusterModel, ClusterConfigData][] {
const validConfigs: ReturnType<typeof configToModels> = [];
for (const { config, error } of splitConfig(rootConfig)) {
if (error) {
logger.debug(`${logPrefix} context failed validation: ${error}`, { context: config.currentContext, filePath });
for (const { config, validationResult } of splitConfig(rootConfig)) {
if (validationResult.error) {
logger.debug(`${logPrefix} context failed validation: ${validationResult.error}`, { context: config.currentContext, filePath });
} else {
validConfigs.push({
kubeConfigPath: filePath,
contextName: config.currentContext,
});
validConfigs.push([
{
kubeConfigPath: filePath,
contextName: config.currentContext,
},
{
clusterServerUrl: validationResult.cluster.server,
},
]);
}
}
@@ -169,7 +175,7 @@ type RootSource = ObservableMap<string, RootSourceValue>;
interface ComputeDiffDependencies {
directoryForKubeConfigs: string;
createCluster: (model: ClusterModel) => Cluster;
createCluster: CreateCluster;
clusterManager: ClusterManager;
}
@@ -184,15 +190,15 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterMan
}
const rawModels = configToModels(config, filePath);
const models = new Map(rawModels.map(m => [m.contextName, m]));
const models = new Map(rawModels.map(([model, configData]) => [model.contextName, [model, configData] as const]));
logger.debug(`${logPrefix} File now has ${models.size} entries`, { filePath });
for (const [contextName, value] of source) {
const model = models.get(contextName);
const data = models.get(contextName);
// remove and disconnect clusters that were removed from the config
if (!model) {
if (!data) {
// remove from the deleting set, so that if a new context of the same name is added, it isn't marked as deleting
clusterManager.deleting.delete(value[0].id);
@@ -207,17 +213,17 @@ export const computeDiff = ({ directoryForKubeConfigs, createCluster, clusterMan
// diff against that
// or update the model and mark it as not needed to be added
value[0].updateModel(model);
value[0].updateModel(data[0]);
models.delete(contextName);
logger.debug(`${logPrefix} Updated old cluster from sync`, { filePath, contextName });
}
for (const [contextName, model] of models) {
for (const [contextName, [model, configData]] of models) {
// add new clusters to the source
try {
const clusterId = createHash("md5").update(`${filePath}:${contextName}`).digest("hex");
const cluster = ClusterStore.getInstance().getById(clusterId) || createCluster({ ...model, id: clusterId });
const cluster = ClusterStore.getInstance().getById(clusterId) || createCluster({ ...model, id: clusterId }, configData);
if (!cluster.apiUrl) {
throw new Error("Cluster constructor failed, see above error");