diff --git a/src/common/ipc-messages.ts b/src/common/ipc-messages.ts index cff804b411..46eba200ae 100644 --- a/src/common/ipc-messages.ts +++ b/src/common/ipc-messages.ts @@ -5,8 +5,7 @@ export enum ClusterIpcMessage { CLUSTER_STOP = "cluster-stop", CLUSTER_REMOVE = "cluster-remove", CLUSTER_REMOVE_WORKSPACE = "cluster-remove-all-from-workspace", - CLUSTER_EVENTS = "cluster-events-count", - CLUSTER_REFRESH = "cluster-refresh", + CLUSTER_REFRESH = "cluster-refresh", // todo: remove, possibly won't needed FEATURE_INSTALL = "cluster-feature-install", FEATURE_UPGRADE = "cluster-feature-upgrade", FEATURE_REMOVE = "cluster-feature-remove", diff --git a/src/common/utils/randomFileName.ts b/src/common/utils/randomFileName.ts index 73d09301be..0a32825f59 100644 --- a/src/common/utils/randomFileName.ts +++ b/src/common/utils/randomFileName.ts @@ -1,5 +1,6 @@ -// Creates system valid random filename +// Create random filename -export function randomFileName(name: string) { - return `${Math.random().toString(36).substring(2, 15)}-${Math.random().toString(36).substring(2, 15)}-${name}` +export function randomFileName({ prefix = "", suffix = "", sep = "__" } = {}) { + const randId = () => Math.random().toString(16).substr(2); + return [prefix, randId(), suffix].filter(s => s).join(sep); } diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index e961719300..c38e3f7622 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -87,7 +87,7 @@ export class ClusterManager { } } - // fixme: verify + // fixme getClusterForRequest(req: http.IncomingMessage): Cluster { let cluster: Cluster = null @@ -131,7 +131,6 @@ export class ClusterManager { } } - // todo: check feature failures protected async installFeature({ clusterId, name, config }: FeatureInstallRequest) { tracker.event("cluster", "install-feature") return this.getCluster(clusterId)?.installFeature(name, config) @@ -147,10 +146,6 @@ export class ClusterManager { return this.getCluster(clusterId)?.uninstallFeature(name); } - protected async getEventsCount(clusterId: ClusterId): Promise { - return await this.getCluster(clusterId)?.getEventCount() || 0; - } - protected async refreshCluster(clusterId: ClusterId) { await this.getCluster(clusterId)?.refreshStatus(); } @@ -161,7 +156,6 @@ export class ClusterManager { [ClusterIpcMessage.CLUSTER_STOP]: clusterManager.stopCluster, [ClusterIpcMessage.CLUSTER_REMOVE]: clusterManager.removeCluster, [ClusterIpcMessage.CLUSTER_REMOVE_WORKSPACE]: clusterManager.removeAllByWorkspace, - [ClusterIpcMessage.CLUSTER_EVENTS]: clusterManager.getEventsCount, [ClusterIpcMessage.CLUSTER_REFRESH]: clusterManager.refreshCluster, [ClusterIpcMessage.FEATURE_INSTALL]: clusterManager.installFeature, [ClusterIpcMessage.FEATURE_UPGRADE]: clusterManager.upgradeFeature, diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 30394785c2..6acb3f5270 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -48,11 +48,11 @@ export class Cluster implements ClusterModel { @observable online: boolean; @observable accessible: boolean; @observable failureReason: string; - @observable nodes: number; + @observable nodes = 0; @observable version: string; - @observable distribution: string; - @observable isAdmin: boolean; - @observable eventCount: number; // todo: auto-fetch every 3s and push updates to client (?) + @observable distribution = "unknown"; + @observable isAdmin = false; + @observable eventCount = 0; // todo: auto-fetch every 3s and push updates to client (?) @observable preferences: ClusterPreferences = {}; @observable features: FeatureStatusMap = {}; @@ -149,7 +149,7 @@ export class Cluster implements ClusterModel { return this.preferences.prometheus?.prefix || "" } - k8sRequest(path: string, options: RequestPromiseOptions = {}) { + protected k8sRequest(path: string, options: RequestPromiseOptions = {}) { return request(this.kubeProxyUrl + path, { json: true, timeout: 10000, @@ -160,7 +160,7 @@ export class Cluster implements ClusterModel { }) } - protected async getConnectionStatus() { + protected async getConnectionStatus(): Promise { try { const response = await this.k8sRequest("/version") this.version = response.gitVersion @@ -198,7 +198,7 @@ export class Cluster implements ClusterModel { kind: "SelfSubjectAccessReview", spec: { resourceAttributes } }) - return accessReview.body.status.allowed === true + return accessReview.body.status.allowed } catch (error) { logger.error(`failed to request selfSubjectAccessReview: ${error.message}`) return false @@ -224,7 +224,7 @@ export class Cluster implements ClusterModel { return "vanilla" } - protected async getNodeCount() { + protected async getNodeCount(): Promise { try { const response = await this.k8sRequest("/api/v1/nodes") return response.items.length @@ -234,7 +234,7 @@ export class Cluster implements ClusterModel { } } - async getEventCount(): Promise { + protected async getEventCount(): Promise { if (!this.isAdmin) { return 0; } diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index b7b0690f03..eff8666051 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -80,7 +80,7 @@ export class ContextHandler { return apiTarget } - // fixme: verify + // fixme public async getApiTargetUrl(): Promise { await this.ensurePort(); return `http://127.0.0.1:${this.proxyPort}${this.clusterUrl.path}`; diff --git a/src/main/k8s.ts b/src/main/k8s.ts index 1e4fefff8d..90c5d78cfa 100644 --- a/src/main/k8s.ts +++ b/src/main/k8s.ts @@ -66,52 +66,52 @@ export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] { return configs; } -export function dumpConfigYaml(kc: KubeConfig): string { +export function dumpConfigYaml(kubeConfig: Partial): string { const config = { apiVersion: "v1", kind: "Config", preferences: {}, - 'current-context': kc.currentContext, - clusters: kc.clusters.map(c => { + 'current-context': kubeConfig.currentContext, + clusters: kubeConfig.clusters.map(cluster => { return { - name: c.name, + name: cluster.name, cluster: { - 'certificate-authority-data': c.caData, - 'certificate-authority': c.caFile, - server: c.server, - 'insecure-skip-tls-verify': c.skipTLSVerify + 'certificate-authority-data': cluster.caData, + 'certificate-authority': cluster.caFile, + server: cluster.server, + 'insecure-skip-tls-verify': cluster.skipTLSVerify } } }), - contexts: kc.contexts.map(c => { + contexts: kubeConfig.contexts.map(context => { return { - name: c.name, + name: context.name, context: { - cluster: c.cluster, - user: c.user, - namespace: c.namespace + cluster: context.cluster, + user: context.user, + namespace: context.namespace } } }), - users: kc.users.map(u => { + users: kubeConfig.users.map(user => { return { - name: u.name, + name: user.name, user: { - 'client-certificate-data': u.certData, - 'client-certificate': u.certFile, - 'client-key-data': u.keyData, - 'client-key': u.keyFile, - 'auth-provider': u.authProvider, - exec: u.exec, - token: u.token, - username: u.username, - password: u.password + 'client-certificate-data': user.certData, + 'client-certificate': user.certFile, + 'client-key-data': user.keyData, + 'client-key': user.keyFile, + 'auth-provider': user.authProvider, + exec: user.exec, + token: user.token, + username: user.username, + password: user.password } } }) } - // logger.info("Dumping KubeConfig:", config); + logger.debug("Dumping KubeConfig:", config); // skipInvalid: true makes dump ignore undefined values return yaml.safeDump(config, { skipInvalid: true }); } diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index c5b0c4547a..a72258d8f4 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -1,22 +1,27 @@ +import type { KubeConfig } from "@kubernetes/client-node"; import type { ContextHandler } from "./context-handler"; import type { Cluster } from "./cluster" import { app } from "electron" +import path from "path" import fs from "fs-extra" -import { randomFileName } from "../common/utils" import { dumpConfigYaml, loadConfig } from "./k8s" import logger from "./logger" export class KubeconfigManager { protected configDir = app.getPath("temp") - protected tempFile: string + protected tempFile: string; constructor(protected cluster: Cluster, protected contextHandler: ContextHandler) { this.init(); } protected async init() { - await this.contextHandler.ensurePort(); - this.tempFile = await this.createTemporaryKubeconfig(); + try { + await this.contextHandler.ensurePort(); + await this.createProxyKubeconfig(); + } catch (err) { + logger.error(`Failed to created temp config`, { err }) + } } getPath() { @@ -27,33 +32,38 @@ export class KubeconfigManager { * Creates new "temporary" kubeconfig that point to the kubectl-proxy. * This way any user of the config does not need to know anything about the auth etc. details. */ - protected async createTemporaryKubeconfig(): Promise { + protected async createProxyKubeconfig(): Promise { fs.ensureDir(this.configDir); - const path = `${this.configDir}/${randomFileName("kubeconfig")}`; - const { contextName, kubeConfigPath } = this.cluster; + const { contextName, kubeConfigPath, id } = this.cluster; + const tempFile = path.join(this.configDir, `kubeconfig-${id}`); const kubeConfig = loadConfig(kubeConfigPath); - kubeConfig.clusters = [ - { - name: contextName, - server: await this.contextHandler.getApiTargetUrl(), - skipTLSVerify: true, - } - ]; - kubeConfig.users = [ - { name: "proxy" }, - ]; - kubeConfig.currentContext = contextName; - kubeConfig.contexts = [ - { - user: "proxy", - name: contextName, - cluster: contextName, - namespace: kubeConfig.getContextObject(contextName).namespace, - } - ]; - logger.info(`Creating temp config for context "${contextName}" at "${path}"`); - fs.writeFileSync(path, dumpConfigYaml(kubeConfig)); - return path; + const proxyUser = "proxy"; + const proxyConfig: Partial = { + currentContext: contextName, + clusters: [ + { + name: contextName, + server: await this.contextHandler.getApiTargetUrl(), + skipTLSVerify: undefined, + } + ], + users: [ + { name: proxyUser }, + ], + contexts: [ + { + user: proxyUser, + name: contextName, + cluster: contextName, + namespace: kubeConfig.getContextObject(contextName).namespace, + } + ] + }; + const tempConfigYaml = dumpConfigYaml(proxyConfig); + fs.writeFileSync(tempFile, tempConfigYaml); + logger.info(`Created temp kubeconfig "${contextName}" at "${tempFile}": \n${tempConfigYaml}`); + this.tempFile = tempFile; + return tempFile; } unlink() {