mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Implement cluster metadata detectors (#1106)
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
parent
99db7aca19
commit
ce995f3deb
@ -90,13 +90,19 @@ export class BaseStore<T = any> extends Singleton {
|
|||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
const callback = (event: IpcRendererEvent, model: T) => {
|
const callback = (event: IpcRendererEvent, model: T) => {
|
||||||
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
||||||
this.onSync(model);
|
this.onSyncFromMain(model);
|
||||||
};
|
};
|
||||||
ipcRenderer.on(this.syncChannel, callback);
|
ipcRenderer.on(this.syncChannel, callback);
|
||||||
this.syncDisposers.push(() => ipcRenderer.off(this.syncChannel, callback));
|
this.syncDisposers.push(() => ipcRenderer.off(this.syncChannel, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onSyncFromMain(model: T) {
|
||||||
|
this.applyWithoutSync(() => {
|
||||||
|
this.onSync(model)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
unregisterIpcListener() {
|
unregisterIpcListener() {
|
||||||
ipcRenderer.removeAllListeners(this.syncChannel)
|
ipcRenderer.removeAllListeners(this.syncChannel)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const clusterIpc = {
|
|||||||
channel: "cluster:refresh",
|
channel: "cluster:refresh",
|
||||||
handle: (clusterId: ClusterId) => {
|
handle: (clusterId: ClusterId) => {
|
||||||
const cluster = clusterStore.getById(clusterId);
|
const cluster = clusterStore.getById(clusterId);
|
||||||
if (cluster) return cluster.refresh();
|
if (cluster) return cluster.refresh({ refreshMetadata: true })
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,10 @@ export interface ClusterIconUpload {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClusterMetadata {
|
||||||
|
[key: string]: string | number | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ClusterStoreModel {
|
export interface ClusterStoreModel {
|
||||||
activeCluster?: ClusterId; // last opened cluster
|
activeCluster?: ClusterId; // last opened cluster
|
||||||
clusters?: ClusterModel[]
|
clusters?: ClusterModel[]
|
||||||
@ -32,6 +36,7 @@ export interface ClusterModel {
|
|||||||
workspace?: WorkspaceId;
|
workspace?: WorkspaceId;
|
||||||
contextName?: string;
|
contextName?: string;
|
||||||
preferences?: ClusterPreferences;
|
preferences?: ClusterPreferences;
|
||||||
|
metadata?: ClusterMetadata;
|
||||||
kubeConfigPath: string;
|
kubeConfigPath: string;
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import { getFreePort } from "../port";
|
|||||||
import { V1ResourceAttributes } from "@kubernetes/client-node";
|
import { V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { apiResources } from "../../common/rbac";
|
import { apiResources } from "../../common/rbac";
|
||||||
import request from "request-promise-native"
|
import request from "request-promise-native"
|
||||||
|
import { Kubectl } from "../kubectl";
|
||||||
|
|
||||||
const mockedRequest = request as jest.MockedFunction<typeof request>
|
const mockedRequest = request as jest.MockedFunction<typeof request>
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ describe("create clusters", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
mockFs(mockOpts)
|
mockFs(mockOpts)
|
||||||
|
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValue(Promise.resolve(true))
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -116,7 +118,7 @@ describe("create clusters", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
jest.spyOn(Cluster.prototype, "isClusterAdmin").mockReturnValue(Promise.resolve(true))
|
||||||
jest.spyOn(Cluster.prototype, "canI")
|
jest.spyOn(Cluster.prototype, "canI")
|
||||||
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
|
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
|
||||||
expect(attr.namespace).toBe("default")
|
expect(attr.namespace).toBe("default")
|
||||||
@ -159,7 +161,7 @@ describe("create clusters", () => {
|
|||||||
expect(c.accessible).toBe(true)
|
expect(c.accessible).toBe(true)
|
||||||
expect(c.allowedNamespaces.length).toBe(1)
|
expect(c.allowedNamespaces.length).toBe(1)
|
||||||
expect(c.allowedResources.length).toBe(apiResources.length)
|
expect(c.allowedResources.length).toBe(apiResources.length)
|
||||||
|
c.disconnect()
|
||||||
jest.resetAllMocks()
|
jest.resetAllMocks()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
33
src/main/cluster-detectors/base-cluster-detector.ts
Normal file
33
src/main/cluster-detectors/base-cluster-detector.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request, { RequestPromiseOptions } from "request-promise-native"
|
||||||
|
import { Cluster } from "../cluster";
|
||||||
|
|
||||||
|
export type ClusterDetectionResult = {
|
||||||
|
value: string | number | boolean
|
||||||
|
accuracy: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BaseClusterDetector {
|
||||||
|
cluster: Cluster
|
||||||
|
key: string
|
||||||
|
|
||||||
|
constructor(cluster: Cluster) {
|
||||||
|
this.cluster = cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
detect(): Promise<ClusterDetectionResult> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
|
||||||
|
const apiUrl = this.cluster.kubeProxyUrl + path;
|
||||||
|
return request(apiUrl, {
|
||||||
|
json: true,
|
||||||
|
timeout: 30000,
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||||
|
...(options.headers || {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/cluster-detectors/cluster-id-detector.ts
Normal file
23
src/main/cluster-detectors/cluster-id-detector.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { createHash } from "crypto"
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class ClusterIdDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.CLUSTER_ID
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
let id: string
|
||||||
|
try {
|
||||||
|
id = await this.getDefaultNamespaceId()
|
||||||
|
} catch(_) {
|
||||||
|
id = this.cluster.apiUrl
|
||||||
|
}
|
||||||
|
const value = createHash("sha256").update(id).digest("hex")
|
||||||
|
return { value: value, accuracy: 100 }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getDefaultNamespaceId() {
|
||||||
|
const response = await this.k8sRequest("/api/v1/namespaces/default")
|
||||||
|
return response.metadata.uid
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/cluster-detectors/detector-registry.ts
Normal file
45
src/main/cluster-detectors/detector-registry.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { observable } from "mobx";
|
||||||
|
import { ClusterMetadata } from "../../common/cluster-store";
|
||||||
|
import { Cluster } from "../cluster";
|
||||||
|
import { BaseClusterDetector, ClusterDetectionResult } from "./base-cluster-detector";
|
||||||
|
import { ClusterIdDetector } from "./cluster-id-detector";
|
||||||
|
import { DistributionDetector } from "./distribution-detector";
|
||||||
|
import { LastSeenDetector } from "./last-seen-detector";
|
||||||
|
import { NodesCountDetector } from "./nodes-count-detector";
|
||||||
|
import { VersionDetector } from "./version-detector";
|
||||||
|
|
||||||
|
export class DetectorRegistry {
|
||||||
|
registry = observable.array<typeof BaseClusterDetector>([], { deep: false });
|
||||||
|
|
||||||
|
add(detectorClass: typeof BaseClusterDetector) {
|
||||||
|
this.registry.push(detectorClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
async detectForCluster(cluster: Cluster): Promise<ClusterMetadata> {
|
||||||
|
const results: {[key: string]: ClusterDetectionResult } = {}
|
||||||
|
for (const detectorClass of this.registry) {
|
||||||
|
const detector = new detectorClass(cluster)
|
||||||
|
try {
|
||||||
|
const data = await detector.detect()
|
||||||
|
if (!data) continue;
|
||||||
|
const existingValue = results[detector.key]
|
||||||
|
if (existingValue && existingValue.accuracy > data.accuracy) continue; // previous value exists and is more accurate
|
||||||
|
results[detector.key] = data
|
||||||
|
} catch (e) {
|
||||||
|
// detector raised error, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const metadata: ClusterMetadata = {}
|
||||||
|
for (const [key, result] of Object.entries(results)) {
|
||||||
|
metadata[key] = result.value
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const detectorRegistry = new DetectorRegistry()
|
||||||
|
detectorRegistry.add(ClusterIdDetector)
|
||||||
|
detectorRegistry.add(LastSeenDetector)
|
||||||
|
detectorRegistry.add(VersionDetector)
|
||||||
|
detectorRegistry.add(DistributionDetector)
|
||||||
|
detectorRegistry.add(NodesCountDetector)
|
||||||
80
src/main/cluster-detectors/distribution-detector.ts
Normal file
80
src/main/cluster-detectors/distribution-detector.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class DistributionDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.DISTRIBUTION
|
||||||
|
version: string
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
this.version = await this.getKubernetesVersion()
|
||||||
|
if (await this.isRancher()) {
|
||||||
|
return { value: "rancher", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isGKE()) {
|
||||||
|
return { value: "gke", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isEKS()) {
|
||||||
|
return { value: "eks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isIKS()) {
|
||||||
|
return { value: "iks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isAKS()) {
|
||||||
|
return { value: "aks", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isDigitalOcean()) {
|
||||||
|
return { value: "digitalocean", accuracy: 90}
|
||||||
|
}
|
||||||
|
if (this.isMinikube()) {
|
||||||
|
return { value: "minikube", accuracy: 80}
|
||||||
|
}
|
||||||
|
if (this.isCustom()) {
|
||||||
|
return { value: "custom", accuracy: 10}
|
||||||
|
}
|
||||||
|
return { value: "vanilla", accuracy: 10}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getKubernetesVersion() {
|
||||||
|
if (this.cluster.version) return this.cluster.version
|
||||||
|
|
||||||
|
const response = await this.k8sRequest("/version")
|
||||||
|
return response.gitVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isGKE() {
|
||||||
|
return this.version.includes("gke")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isEKS() {
|
||||||
|
return this.version.includes("eks")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isIKS() {
|
||||||
|
return this.version.includes("IKS")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isAKS() {
|
||||||
|
return this.cluster.apiUrl.endsWith("azmk8s.io")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isDigitalOcean() {
|
||||||
|
return this.cluster.apiUrl.endsWith("k8s.ondigitalocean.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isMinikube() {
|
||||||
|
return this.cluster.contextName.startsWith("minikube")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected isCustom() {
|
||||||
|
return this.version.includes("+")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async isRancher() {
|
||||||
|
try {
|
||||||
|
const response = await this.k8sRequest("")
|
||||||
|
return response.data.find((api: any) => api?.apiVersion?.group === "meta.cattle.io") !== undefined
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/cluster-detectors/last-seen-detector.ts
Normal file
13
src/main/cluster-detectors/last-seen-detector.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class LastSeenDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.LAST_SEEN
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
if (!this.cluster.accessible) return null;
|
||||||
|
|
||||||
|
await this.k8sRequest("/version")
|
||||||
|
return { value: new Date().toJSON(), accuracy: 100 }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/cluster-detectors/nodes-count-detector.ts
Normal file
18
src/main/cluster-detectors/nodes-count-detector.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class NodesCountDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.NODES_COUNT
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
const nodeCount = await this.getNodeCount()
|
||||||
|
return { value: nodeCount, accuracy: 100}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async getNodeCount(): Promise<number> {
|
||||||
|
if (!this.cluster.accessible) return null;
|
||||||
|
|
||||||
|
const response = await this.k8sRequest("/api/v1/nodes")
|
||||||
|
return response.items.length
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/cluster-detectors/version-detector.ts
Normal file
17
src/main/cluster-detectors/version-detector.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { BaseClusterDetector } from "./base-cluster-detector";
|
||||||
|
import { ClusterMetadataKey } from "../cluster";
|
||||||
|
|
||||||
|
export class VersionDetector extends BaseClusterDetector {
|
||||||
|
key = ClusterMetadataKey.VERSION
|
||||||
|
value: string
|
||||||
|
|
||||||
|
public async detect() {
|
||||||
|
const version = await this.getKubernetesVersion()
|
||||||
|
return { value: version, accuracy: 100}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getKubernetesVersion() {
|
||||||
|
const response = await this.k8sRequest("/version")
|
||||||
|
return response.gitVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||||
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
|
||||||
import type { WorkspaceId } from "../common/workspace-store";
|
import type { WorkspaceId } from "../common/workspace-store";
|
||||||
import type { FeatureStatusMap } from "./feature"
|
import type { FeatureStatusMap } from "./feature"
|
||||||
@ -14,6 +14,8 @@ import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from ".
|
|||||||
import request, { RequestPromiseOptions } from "request-promise-native"
|
import request, { RequestPromiseOptions } from "request-promise-native"
|
||||||
import { apiResources } from "../common/rbac";
|
import { apiResources } from "../common/rbac";
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
|
import { VersionDetector } from "./cluster-detectors/version-detector";
|
||||||
|
import { detectorRegistry } from "./cluster-detectors/detector-registry";
|
||||||
|
|
||||||
export enum ClusterStatus {
|
export enum ClusterStatus {
|
||||||
AccessGranted = 2,
|
AccessGranted = 2,
|
||||||
@ -21,6 +23,18 @@ export enum ClusterStatus {
|
|||||||
Offline = 0
|
Offline = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ClusterMetadataKey {
|
||||||
|
VERSION = "version",
|
||||||
|
CLUSTER_ID = "id",
|
||||||
|
DISTRIBUTION = "distribution",
|
||||||
|
NODES_COUNT = "nodes",
|
||||||
|
LAST_SEEN = "lastSeen"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClusterRefreshOptions = {
|
||||||
|
refreshMetadata?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ClusterState extends ClusterModel {
|
export interface ClusterState extends ClusterModel {
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
@ -29,10 +43,7 @@ export interface ClusterState extends ClusterModel {
|
|||||||
accessible: boolean;
|
accessible: boolean;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
failureReason: string;
|
failureReason: string;
|
||||||
nodes: number;
|
|
||||||
eventCount: number;
|
eventCount: number;
|
||||||
version: string;
|
|
||||||
distribution: string;
|
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
allowedNamespaces: string[]
|
allowedNamespaces: string[]
|
||||||
allowedResources: string[]
|
allowedResources: string[]
|
||||||
@ -63,12 +74,10 @@ export class Cluster implements ClusterModel {
|
|||||||
@observable reconnecting = false;
|
@observable reconnecting = false;
|
||||||
@observable disconnected = true;
|
@observable disconnected = true;
|
||||||
@observable failureReason: string;
|
@observable failureReason: string;
|
||||||
@observable nodes = 0;
|
|
||||||
@observable version: string;
|
|
||||||
@observable distribution = "unknown";
|
|
||||||
@observable isAdmin = false;
|
@observable isAdmin = false;
|
||||||
@observable eventCount = 0;
|
@observable eventCount = 0;
|
||||||
@observable preferences: ClusterPreferences = {};
|
@observable preferences: ClusterPreferences = {};
|
||||||
|
@observable metadata: ClusterMetadata = {};
|
||||||
@observable features: FeatureStatusMap = {};
|
@observable features: FeatureStatusMap = {};
|
||||||
@observable allowedNamespaces: string[] = [];
|
@observable allowedNamespaces: string[] = [];
|
||||||
@observable allowedResources: string[] = [];
|
@observable allowedResources: string[] = [];
|
||||||
@ -76,6 +85,9 @@ export class Cluster implements ClusterModel {
|
|||||||
@computed get available() {
|
@computed get available() {
|
||||||
return this.accessible && !this.disconnected;
|
return this.accessible && !this.disconnected;
|
||||||
}
|
}
|
||||||
|
get version(): string {
|
||||||
|
return String(this.metadata?.version) || ""
|
||||||
|
}
|
||||||
|
|
||||||
constructor(model: ClusterModel) {
|
constructor(model: ClusterModel) {
|
||||||
this.updateModel(model);
|
this.updateModel(model);
|
||||||
@ -113,10 +125,14 @@ export class Cluster implements ClusterModel {
|
|||||||
protected bindEvents() {
|
protected bindEvents() {
|
||||||
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||||
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
|
||||||
|
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
|
||||||
|
|
||||||
this.eventDisposers.push(
|
this.eventDisposers.push(
|
||||||
reaction(this.getState, this.pushState),
|
reaction(this.getState, this.pushState),
|
||||||
() => clearInterval(refreshTimer),
|
() => {
|
||||||
|
clearInterval(refreshTimer);
|
||||||
|
clearInterval(refreshMetadataTimer);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +158,7 @@ export class Cluster implements ClusterModel {
|
|||||||
await this.refreshConnectionStatus()
|
await this.refreshConnectionStatus()
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
await this.refreshAllowedResources()
|
await this.refreshAllowedResources()
|
||||||
|
this.isAdmin = await this.isClusterAdmin()
|
||||||
this.ready = true
|
this.ready = true
|
||||||
this.kubeCtl = new Kubectl(this.version)
|
this.kubeCtl = new Kubectl(this.version)
|
||||||
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
||||||
@ -172,29 +189,37 @@ export class Cluster implements ClusterModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async refresh() {
|
async refresh(opts: ClusterRefreshOptions = {}) {
|
||||||
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||||
await this.whenInitialized;
|
await this.whenInitialized;
|
||||||
await this.refreshConnectionStatus();
|
await this.refreshConnectionStatus();
|
||||||
if (this.accessible) {
|
if (this.accessible) {
|
||||||
this.distribution = this.detectKubernetesDistribution(this.version)
|
const [features, isAdmin] = await Promise.all([
|
||||||
const [features, isAdmin, nodesCount] = await Promise.all([
|
|
||||||
getFeatures(this),
|
getFeatures(this),
|
||||||
this.isClusterAdmin(),
|
this.isClusterAdmin(),
|
||||||
this.getNodeCount(),
|
|
||||||
]);
|
]);
|
||||||
this.features = features;
|
this.features = features;
|
||||||
this.isAdmin = isAdmin;
|
this.isAdmin = isAdmin;
|
||||||
this.nodes = nodesCount;
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.refreshEvents(),
|
this.refreshEvents(),
|
||||||
this.refreshAllowedResources(),
|
this.refreshAllowedResources(),
|
||||||
]);
|
]);
|
||||||
|
if (opts.refreshMetadata) {
|
||||||
|
this.refreshMetadata()
|
||||||
|
}
|
||||||
this.ready = true
|
this.ready = true
|
||||||
}
|
}
|
||||||
this.pushState();
|
this.pushState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async refreshMetadata() {
|
||||||
|
logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
|
||||||
|
const metadata = await detectorRegistry.detectForCluster(this)
|
||||||
|
const existingMetadata = this.metadata
|
||||||
|
this.metadata = Object.assign(existingMetadata, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async refreshConnectionStatus() {
|
async refreshConnectionStatus() {
|
||||||
const connectionStatus = await this.getConnectionStatus();
|
const connectionStatus = await this.getConnectionStatus();
|
||||||
@ -263,9 +288,9 @@ export class Cluster implements ClusterModel {
|
|||||||
|
|
||||||
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
protected async getConnectionStatus(): Promise<ClusterStatus> {
|
||||||
try {
|
try {
|
||||||
const response = await this.k8sRequest("/version")
|
const versionDetector = new VersionDetector(this)
|
||||||
this.version = response.gitVersion
|
const versionData = await versionDetector.detect()
|
||||||
this.failureReason = null
|
this.metadata.version = versionData.value
|
||||||
return ClusterStatus.AccessGranted;
|
return ClusterStatus.AccessGranted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to connect cluster "${this.contextName}": ${error}`)
|
logger.error(`Failed to connect cluster "${this.contextName}": ${error}`)
|
||||||
@ -314,27 +339,6 @@ export class Cluster implements ClusterModel {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
protected detectKubernetesDistribution(kubernetesVersion: string): string {
|
|
||||||
if (kubernetesVersion.includes("gke")) return "gke"
|
|
||||||
if (kubernetesVersion.includes("eks")) return "eks"
|
|
||||||
if (kubernetesVersion.includes("IKS")) return "iks"
|
|
||||||
if (this.apiUrl.endsWith("azmk8s.io")) return "aks"
|
|
||||||
if (this.apiUrl.endsWith("k8s.ondigitalocean.com")) return "digitalocean"
|
|
||||||
if (this.contextName.startsWith("minikube")) return "minikube"
|
|
||||||
if (kubernetesVersion.includes("+")) return "custom"
|
|
||||||
return "vanilla"
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getNodeCount(): Promise<number> {
|
|
||||||
try {
|
|
||||||
const response = await this.k8sRequest("/api/v1/nodes")
|
|
||||||
return response.items.length
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug(`failed to request node list: ${error.message}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getEventCount(): Promise<number> {
|
protected async getEventCount(): Promise<number> {
|
||||||
if (!this.isAdmin) {
|
if (!this.isAdmin) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -377,6 +381,7 @@ export class Cluster implements ClusterModel {
|
|||||||
kubeConfigPath: this.kubeConfigPath,
|
kubeConfigPath: this.kubeConfigPath,
|
||||||
workspace: this.workspace,
|
workspace: this.workspace,
|
||||||
preferences: this.preferences,
|
preferences: this.preferences,
|
||||||
|
metadata: this.metadata,
|
||||||
};
|
};
|
||||||
return toJS(model, {
|
return toJS(model, {
|
||||||
recurseEverything: true
|
recurseEverything: true
|
||||||
@ -394,9 +399,6 @@ export class Cluster implements ClusterModel {
|
|||||||
disconnected: this.disconnected,
|
disconnected: this.disconnected,
|
||||||
accessible: this.accessible,
|
accessible: this.accessible,
|
||||||
failureReason: this.failureReason,
|
failureReason: this.failureReason,
|
||||||
nodes: this.nodes,
|
|
||||||
version: this.version,
|
|
||||||
distribution: this.distribution,
|
|
||||||
isAdmin: this.isAdmin,
|
isAdmin: this.isAdmin,
|
||||||
features: this.features,
|
features: this.features,
|
||||||
eventCount: this.eventCount,
|
eventCount: this.eventCount,
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export class ClusterSettings extends React.Component<Props> {
|
|||||||
refreshCluster = async () => {
|
refreshCluster = async () => {
|
||||||
if(this.cluster) {
|
if(this.cluster) {
|
||||||
await clusterIpc.activate.invokeFromRenderer(this.cluster.id);
|
await clusterIpc.activate.invokeFromRenderer(this.cluster.id);
|
||||||
clusterIpc.refresh.invokeFromRenderer(this.cluster.id);
|
await clusterIpc.refresh.invokeFromRenderer(this.cluster.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,10 @@ export class Status extends React.Component<Props> {
|
|||||||
const { cluster } = this.props;
|
const { cluster } = this.props;
|
||||||
const rows = [
|
const rows = [
|
||||||
["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`],
|
["Online Status", cluster.online ? "online" : `offline (${cluster.failureReason || "unknown reason"})`],
|
||||||
["Distribution", cluster.distribution],
|
["Distribution", cluster.metadata.distribution ? String(cluster.metadata.distribution) : "N/A"],
|
||||||
["Kernel Version", cluster.version],
|
["Kernel Version", cluster.metadata.version ? String(cluster.metadata.version) : "N/A"],
|
||||||
["API Address", cluster.apiUrl],
|
["API Address", cluster.apiUrl || "N/A"],
|
||||||
["Nodes Count", cluster.nodes || "0"]
|
["Nodes Count", cluster.metadata.nodes ? String(cluster.metadata.nodes) : "N/A"]
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Table scrollable={false}>
|
<Table scrollable={false}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user