mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
258 lines
8.6 KiB
TypeScript
258 lines
8.6 KiB
TypeScript
/**
|
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
*/
|
|
|
|
import "../../common/ipc/cluster";
|
|
import type { IComputedValue, IObservableValue, ObservableSet } from "mobx";
|
|
import { action, makeObservable, observe, reaction, toJS } from "mobx";
|
|
import type { Cluster } from "../../common/cluster/cluster";
|
|
import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster";
|
|
import { ipcMainOn } from "../../common/ipc";
|
|
import { once } from "lodash";
|
|
import type { ClusterId } from "../../common/cluster-types";
|
|
import type { CatalogEntityRegistry } from "../catalog";
|
|
import type { Logger } from "@k8slens/logger";
|
|
import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable";
|
|
import type { UpdateEntitySpec } from "./update-entity-spec.injectable";
|
|
import type { ClusterConnection } from "./cluster-connection.injectable";
|
|
import type { GetClusterById } from "../../features/cluster/storage/common/get-by-id.injectable";
|
|
import type { AddCluster } from "../../features/cluster/storage/common/add.injectable";
|
|
|
|
const logPrefix = "[CLUSTER-MANAGER]:";
|
|
|
|
const lensSpecificClusterStatuses: Set<string> = new Set(Object.values(LensKubernetesClusterStatus));
|
|
|
|
interface Dependencies {
|
|
readonly catalogEntityRegistry: CatalogEntityRegistry;
|
|
readonly clustersThatAreBeingDeleted: ObservableSet<ClusterId>;
|
|
readonly visibleCluster: IObservableValue<ClusterId | null>;
|
|
readonly logger: Logger;
|
|
readonly clusters: IComputedValue<Cluster[]>;
|
|
updateEntityMetadata: UpdateEntityMetadata;
|
|
updateEntitySpec: UpdateEntitySpec;
|
|
getClusterConnection: (cluster: Cluster) => ClusterConnection;
|
|
getClusterById: GetClusterById;
|
|
addCluster: AddCluster;
|
|
}
|
|
|
|
export class ClusterManager {
|
|
constructor(private readonly dependencies: Dependencies) {
|
|
makeObservable(this);
|
|
}
|
|
|
|
init = once(() => {
|
|
// reacting to every cluster's state change and total amount of items
|
|
reaction(
|
|
() => this.dependencies.clusters.get().map(c => c.getState()),
|
|
() => this.updateCatalog(this.dependencies.clusters.get()),
|
|
{ fireImmediately: false },
|
|
);
|
|
|
|
// reacting to every cluster's preferences change and total amount of items
|
|
reaction(
|
|
() => this.dependencies.clusters.get().map(c => toJS(c.preferences)),
|
|
() => this.updateCatalog(this.dependencies.clusters.get()),
|
|
{ fireImmediately: false },
|
|
);
|
|
|
|
reaction(
|
|
() => this.dependencies.catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
|
|
entities => this.syncClustersFromCatalog(entities),
|
|
);
|
|
|
|
reaction(() => [
|
|
this.dependencies.catalogEntityRegistry.filterItemsByPredicate(isKubernetesCluster),
|
|
this.dependencies.visibleCluster.get(),
|
|
] as const, ([entities, visibleCluster]) => {
|
|
for (const entity of entities) {
|
|
if (entity.getId() === visibleCluster) {
|
|
entity.status.active = true;
|
|
} else {
|
|
entity.status.active = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
observe(this.dependencies.clustersThatAreBeingDeleted, change => {
|
|
if (change.type === "add") {
|
|
this.updateEntityStatus(this.dependencies.catalogEntityRegistry.findById(change.newValue) as KubernetesCluster);
|
|
}
|
|
});
|
|
|
|
ipcMainOn("network:offline", this.onNetworkOffline);
|
|
ipcMainOn("network:online", this.onNetworkOnline);
|
|
});
|
|
|
|
@action
|
|
protected updateCatalog(clusters: Cluster[]) {
|
|
this.dependencies.logger.debug("[CLUSTER-MANAGER]: updating catalog from cluster store");
|
|
|
|
for (const cluster of clusters) {
|
|
this.updateEntityFromCluster(cluster);
|
|
}
|
|
}
|
|
|
|
protected updateEntityFromCluster(cluster: Cluster) {
|
|
const index = this.dependencies.catalogEntityRegistry.items.findIndex((entity) => entity.getId() === cluster.id);
|
|
|
|
if (index === -1) {
|
|
return;
|
|
}
|
|
|
|
const entity = this.dependencies.catalogEntityRegistry.items[index] as KubernetesCluster;
|
|
|
|
this.updateEntityStatus(entity, cluster);
|
|
|
|
this.dependencies.updateEntityMetadata(entity, cluster);
|
|
this.dependencies.updateEntitySpec(entity, cluster);
|
|
|
|
this.dependencies.catalogEntityRegistry.items.splice(index, 1, entity);
|
|
}
|
|
|
|
@action
|
|
protected updateEntityStatus(entity: KubernetesCluster, cluster?: Cluster) {
|
|
if (this.dependencies.clustersThatAreBeingDeleted.has(entity.getId())) {
|
|
entity.status.phase = LensKubernetesClusterStatus.DELETING;
|
|
entity.status.enabled = false;
|
|
} else {
|
|
entity.status.phase = (() => {
|
|
if (!cluster) {
|
|
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="no cluster"`);
|
|
|
|
return LensKubernetesClusterStatus.DISCONNECTED;
|
|
}
|
|
|
|
if (cluster.accessible.get()) {
|
|
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to CONNECTED, reason="cluster is accessible"`);
|
|
|
|
return LensKubernetesClusterStatus.CONNECTED;
|
|
}
|
|
|
|
if (!cluster.disconnected.get()) {
|
|
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to CONNECTING, reason="cluster is not disconnected"`);
|
|
|
|
return LensKubernetesClusterStatus.CONNECTING;
|
|
}
|
|
|
|
// Extensions are not allowed to use the Lens specific status phases
|
|
if (!lensSpecificClusterStatuses.has(entity?.status?.phase)) {
|
|
this.dependencies.logger.silly(`${logPrefix} not clearing entity ${entity.getName()} status, reason="custom string"`);
|
|
|
|
return entity.status.phase;
|
|
}
|
|
|
|
this.dependencies.logger.silly(`${logPrefix} setting entity ${entity.getName()} to DISCONNECTED, reason="fallthrough"`);
|
|
|
|
return LensKubernetesClusterStatus.DISCONNECTED;
|
|
})();
|
|
|
|
entity.status.enabled = true;
|
|
}
|
|
}
|
|
|
|
@action
|
|
protected syncClustersFromCatalog(entities: KubernetesCluster[]) {
|
|
for (const entity of entities) {
|
|
const cluster = this.dependencies.getClusterById(entity.getId());
|
|
|
|
if (!cluster) {
|
|
this.dependencies.addCluster({
|
|
id: entity.getId(),
|
|
kubeConfigPath: entity.spec.kubeconfigPath,
|
|
contextName: entity.spec.kubeconfigContext,
|
|
accessibleNamespaces: entity.spec.accessibleNamespaces ?? [],
|
|
});
|
|
} else {
|
|
cluster.kubeConfigPath.set(entity.spec.kubeconfigPath);
|
|
cluster.contextName.set(entity.spec.kubeconfigContext);
|
|
|
|
if (entity.spec.accessibleNamespaces) {
|
|
cluster.accessibleNamespaces.replace(entity.spec.accessibleNamespaces);
|
|
}
|
|
|
|
if (entity.spec.metrics) {
|
|
const { source, prometheus } = entity.spec.metrics;
|
|
|
|
if (source !== "local" && prometheus) {
|
|
const { type, address } = prometheus;
|
|
|
|
if (type) {
|
|
cluster.preferences.prometheusProvider = { type };
|
|
}
|
|
|
|
if (address) {
|
|
cluster.preferences.prometheus = address;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.updateEntityFromCluster(cluster);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected onNetworkOffline = async () => {
|
|
this.dependencies.logger.info(`${logPrefix} network is offline`);
|
|
|
|
await Promise.allSettled(
|
|
this.dependencies
|
|
.clusters
|
|
.get()
|
|
.filter(cluster => !cluster.disconnected.get())
|
|
.map(async (cluster) => {
|
|
cluster.online.set(false);
|
|
cluster.accessible.set(false);
|
|
|
|
await this.dependencies
|
|
.getClusterConnection(cluster)
|
|
.refreshConnectionStatus();
|
|
}),
|
|
);
|
|
};
|
|
|
|
protected onNetworkOnline = async () => {
|
|
this.dependencies.logger.info(`${logPrefix} network is online`);
|
|
|
|
await Promise.allSettled(
|
|
this.dependencies
|
|
.clusters
|
|
.get()
|
|
.filter(cluster => !cluster.disconnected.get())
|
|
.map((cluster) => (
|
|
this.dependencies
|
|
.getClusterConnection(cluster)
|
|
.refreshConnectionStatus()
|
|
)),
|
|
);
|
|
};
|
|
}
|
|
|
|
export function catalogEntityFromCluster(cluster: Cluster) {
|
|
return new KubernetesCluster({
|
|
metadata: {
|
|
uid: cluster.id,
|
|
name: cluster.name.get(),
|
|
source: "local",
|
|
labels: {
|
|
...cluster.labels,
|
|
},
|
|
distro: cluster.distribution.get(),
|
|
kubeVersion: cluster.version.get(),
|
|
},
|
|
spec: {
|
|
kubeconfigPath: cluster.kubeConfigPath.get(),
|
|
kubeconfigContext: cluster.contextName.get(),
|
|
icon: {},
|
|
},
|
|
status: {
|
|
phase: cluster.disconnected.get()
|
|
? LensKubernetesClusterStatus.DISCONNECTED
|
|
: LensKubernetesClusterStatus.CONNECTED,
|
|
reason: "",
|
|
message: "",
|
|
active: !cluster.disconnected.get(),
|
|
},
|
|
});
|
|
}
|