Implement cluster metadata detectors (#1106)

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Lauri Nevala authored and GitHub committed 2020-10-22 21:45:54 +03:00
1 parent 99db7aca19
commit ce995f3deb
14 files changed
+293 -49

No files matched your search

+42 -40
View File
@@ -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 { WorkspaceId } from "../common/workspace-store";
import type { FeatureStatusMap } from "./feature"
@@ -14,6 +14,8 @@ import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from ".
import request, { RequestPromiseOptions } from "request-promise-native"
import { apiResources } from "../common/rbac";
import logger from "./logger"
import { VersionDetector } from "./cluster-detectors/version-detector";
import { detectorRegistry } from "./cluster-detectors/detector-registry";
export enum ClusterStatus {
AccessGranted = 2,
@@ -21,6 +23,18 @@ export enum ClusterStatus {
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 {
initialized: boolean;
apiUrl: string;
@@ -29,10 +43,7 @@ export interface ClusterState extends ClusterModel {
accessible: boolean;
ready: boolean;
failureReason: string;
nodes: number;
eventCount: number;
version: string;
distribution: string;
isAdmin: boolean;
allowedNamespaces: string[]
allowedResources: string[]
@@ -63,12 +74,10 @@ export class Cluster implements ClusterModel {
@observable reconnecting = false;
@observable disconnected = true;
@observable failureReason: string;
@observable nodes = 0;
@observable version: string;
@observable distribution = "unknown";
@observable isAdmin = false;
@observable eventCount = 0;
@observable preferences: ClusterPreferences = {};
@observable metadata: ClusterMetadata = {};
@observable features: FeatureStatusMap = {};
@observable allowedNamespaces: string[] = [];
@observable allowedResources: string[] = [];
@@ -76,6 +85,9 @@ export class Cluster implements ClusterModel {
@computed get available() {
return this.accessible && !this.disconnected;
}
get version(): string {
return String(this.metadata?.version) || ""
}
constructor(model: ClusterModel) {
this.updateModel(model);
@@ -113,10 +125,14 @@ export class Cluster implements ClusterModel {
protected bindEvents() {
logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
const refreshMetadataTimer = setInterval(() => !this.disconnected && this.refreshMetadata(), 900000); // every 15 minutes
this.eventDisposers.push(
reaction(this.getState, this.pushState),
() => clearInterval(refreshTimer),
() => {
clearInterval(refreshTimer);
clearInterval(refreshMetadataTimer);
},
);
}
@@ -142,6 +158,7 @@ export class Cluster implements ClusterModel {
await this.refreshConnectionStatus()
if (this.accessible) {
await this.refreshAllowedResources()
this.isAdmin = await this.isClusterAdmin()
this.ready = true
this.kubeCtl = new Kubectl(this.version)
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
@@ -172,29 +189,37 @@ export class Cluster implements ClusterModel {
}
@action
async refresh() {
async refresh(opts: ClusterRefreshOptions = {}) {
logger.info(`[CLUSTER]: refresh`, this.getMeta());
await this.whenInitialized;
await this.refreshConnectionStatus();
if (this.accessible) {
this.distribution = this.detectKubernetesDistribution(this.version)
const [features, isAdmin, nodesCount] = await Promise.all([
const [features, isAdmin] = await Promise.all([
getFeatures(this),
this.isClusterAdmin(),
this.getNodeCount(),
]);
this.features = features;
this.isAdmin = isAdmin;
this.nodes = nodesCount;
await Promise.all([
this.refreshEvents(),
this.refreshAllowedResources(),
]);
if (opts.refreshMetadata) {
this.refreshMetadata()
}
this.ready = true
}
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
async refreshConnectionStatus() {
const connectionStatus = await this.getConnectionStatus();
@@ -263,9 +288,9 @@ export class Cluster implements ClusterModel {
protected async getConnectionStatus(): Promise<ClusterStatus> {
try {
const response = await this.k8sRequest("/version")
this.version = response.gitVersion
this.failureReason = null
const versionDetector = new VersionDetector(this)
const versionData = await versionDetector.detect()
this.metadata.version = versionData.value
return ClusterStatus.AccessGranted;
} catch (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> {
if (!this.isAdmin) {
return 0;
@@ -377,6 +381,7 @@ export class Cluster implements ClusterModel {
kubeConfigPath: this.kubeConfigPath,
workspace: this.workspace,
preferences: this.preferences,
metadata: this.metadata,
};
return toJS(model, {
recurseEverything: true
@@ -394,9 +399,6 @@ export class Cluster implements ClusterModel {
disconnected: this.disconnected,
accessible: this.accessible,
failureReason: this.failureReason,
nodes: this.nodes,
version: this.version,
distribution: this.distribution,
isAdmin: this.isAdmin,
features: this.features,
eventCount: this.eventCount,