From e72e28ddab733199af4950ebc390ece3049970b6 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 11 Jul 2020 11:32:29 +0300 Subject: [PATCH] refactoring Signed-off-by: Roman --- src/common/cluster-store.ts | 7 +- src/common/utils/cloneJson.ts | 5 ++ src/common/utils/index.ts | 1 + src/common/utils/kubeconfig.ts | 5 +- src/main/cluster-manager.ts | 1 + src/main/cluster.ts | 20 +++--- src/main/context-handler.ts | 32 ++------- src/main/feature.ts | 70 ++++++++---------- src/main/helm-release-manager.ts | 2 +- src/main/k8s.ts | 18 +---- src/main/kube-auth-proxy.ts | 2 +- src/main/kubeconfig-manager.ts | 23 ++---- src/main/kubectl.ts | 2 +- src/main/lens-proxy.ts | 25 +++---- src/main/node-shell-session.ts | 22 +++--- src/main/port.ts | 2 + src/main/resource-applier-api.ts | 6 +- src/main/resource-applier.ts | 75 ++++++++------------ src/main/routes/metrics-route.ts | 4 +- src/main/routes/port-forward-route.ts | 2 +- src/main/shell-session.ts | 4 +- src/migrations/cluster-store/3.6.0-beta.1.ts | 23 +++--- 22 files changed, 133 insertions(+), 218 deletions(-) create mode 100644 src/common/utils/cloneJson.ts diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 9f151a7286..9d1cf09f7a 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -13,11 +13,12 @@ export type ClusterId = string; export interface ClusterModel { id: ClusterId; - contextName: string; - kubeConfigPath: string; - kubeConfig?: string; workspace?: string; preferences?: ClusterPreferences; + kubeConfigPath: string; + + /** @deprecated */ + kubeConfig?: string; // kube-config yaml } export interface ClusterPreferences { diff --git a/src/common/utils/cloneJson.ts b/src/common/utils/cloneJson.ts new file mode 100644 index 0000000000..d44f9c7898 --- /dev/null +++ b/src/common/utils/cloneJson.ts @@ -0,0 +1,5 @@ +// Clone json-serializable object + +export function cloneJsonObject(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index a5a2d9e3f5..f3c23800e8 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -4,3 +4,4 @@ export * from "./base64" export * from "./camelCase" export * from "./splitArray" export * from "./randomFileName" +export * from "./cloneJson" diff --git a/src/common/utils/kubeconfig.ts b/src/common/utils/kubeconfig.ts index a6f7015515..17bdf3bb90 100644 --- a/src/common/utils/kubeconfig.ts +++ b/src/common/utils/kubeconfig.ts @@ -2,11 +2,8 @@ import { app, remote } from "electron" import { ensureDirSync, writeFileSync } from "fs-extra" import * as path from "path" -// todo: move to main/kubeconfig-manager.ts (?) - -// Writes kubeconfigs to "embedded" store, i.e. .../Lens/kubeconfigs/ +// Write kubeconfigs to "embedded" store, i.e. "/Users/ixrock/Library/Application Support/Lens/kubeconfigs" export function writeEmbeddedKubeConfig(clusterId: string, kubeConfig: string): string { - // This can be called from main & renderer const userData = (app || remote.app).getPath("userData"); const kubeConfigBase = path.join(userData, "kubeconfigs") ensureDirSync(kubeConfigBase) diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index be1059cead..1dc161d2b5 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -89,6 +89,7 @@ export class ClusterManager { } } + // fixme getClusterForRequest(req: http.IncomingMessage): Cluster { let cluster: Cluster = null diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 52f2679395..9d299cfa9e 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -1,6 +1,6 @@ -import url, { UrlWithStringQuery } from "url" import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store" import type { FeatureStatusMap } from "./feature" +import { UrlWithStringQuery } from "url" import { action, observable, toJS } from "mobx"; import { ContextHandler } from "./context-handler" import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node" @@ -31,16 +31,15 @@ export interface ClusterState extends ClusterModel { } export class Cluster implements ClusterModel { + public id: ClusterId; public kubeCtl: Kubectl public contextHandler: ContextHandler; protected kubeconfigManager: KubeconfigManager; @observable initialized = false; - @observable id: ClusterId; @observable workspace: string; - @observable kubeConfig?: string; - @observable kubeConfigPath: string; @observable contextName: string; + @observable kubeConfigPath: string; @observable port: number; @observable url: string; // cluster-api url @observable apiUrl: UrlWithStringQuery; // same as url, but parsed @@ -63,20 +62,23 @@ export class Cluster implements ClusterModel { updateModel(model: ClusterModel) { Object.assign(this, model); + if (!this.contextName) { + this.contextName = this.preferences.clusterName; + } } @action - // fixme: completely broken async init() { try { + // fixme: all broken this.contextHandler = new ContextHandler(this); - this.contextName = this.contextHandler.contextName; this.port = await this.contextHandler.resolveProxyPort(); // resolve port before KubeconfigManager this.webContentUrl = `http://${this.id}.localhost:${this.port}`; this.kubeAuthProxyUrl = `http://127.0.0.1:${this.port}`; this.kubeconfigManager = new KubeconfigManager(this); - this.url = this.kubeconfigManager.getCurrentClusterServer(); - this.apiUrl = url.parse(this.url); + // this.url = this.kubeconfigManager.getCurrentClusterServer(); + // this.apiUrl = url.parse(this.url); + logger.info(`[CLUSTER]: init success`, { id: this.id, port: this.port, @@ -102,7 +104,7 @@ export class Cluster implements ClusterModel { // todo: auto-refresh when preferences changed + by timer? @action async refreshCluster() { - this.contextHandler.setClusterPreferences(this.preferences); + this.contextHandler.setupPrometheus(this.preferences); const connectionStatus = await this.getConnectionStatus() this.accessible = connectionStatus == ClusterStatus.AccessGranted; diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index a5d1474e52..ac2bc8b6fe 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -9,29 +9,18 @@ import { getFreePort } from "./port" import { KubeAuthProxy } from "./kube-auth-proxy" export class ContextHandler { - public url: string public proxyPort: number - public contextName: string - protected id: string protected proxyServer: KubeAuthProxy protected apiTarget: ServerOptions - protected certData: string - protected authCertData: string - protected proxyTarget: ServerOptions - protected clientCert: string - protected clientKey: string protected prometheusProvider: string protected prometheusPath: string constructor(protected cluster: Cluster) { - this.id = cluster.id - this.url = cluster.url; - this.contextName = cluster.contextName || cluster.preferences.clusterName; - this.setClusterPreferences(cluster.preferences) + this.setupPrometheus(cluster.preferences) } - public setClusterPreferences(preferences: ClusterPreferences = {}) { + public setupPrometheus(preferences: ClusterPreferences = {}) { this.prometheusProvider = preferences.prometheusProvider?.type; this.prometheusPath = null; if (preferences.prometheus) { @@ -89,6 +78,7 @@ export class ContextHandler { return apiTarget } + // fixme protected async newApiTarget(timeout: number): Promise { return { changeOrigin: true, @@ -107,27 +97,19 @@ export class ContextHandler { async resolveProxyPort(): Promise { if (!this.proxyPort) { - this.proxyPort = await getFreePort() + this.proxyPort = await getFreePort(); } return this.proxyPort } - public async withTemporaryKubeconfig(callback: (kubeconfig: string) => Promise) { - try { - await callback(this.cluster.proxyKubeconfigPath()) - } catch (error) { - throw(error) - } - } - public async ensureServer() { if (!this.proxyServer) { - const proxyPort = await this.resolveProxyPort() + await this.resolveProxyPort(); const proxyEnv = Object.assign({}, process.env) if (this.cluster.preferences.httpsProxy) { proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy } - this.proxyServer = new KubeAuthProxy(this.cluster, proxyPort, proxyEnv) + this.proxyServer = new KubeAuthProxy(this.cluster, this.proxyPort, proxyEnv) await this.proxyServer.run() } } @@ -139,7 +121,7 @@ export class ContextHandler { } } - public proxyServerError() { + public proxyServerError(): string { return this.proxyServer?.lastError || "" } } diff --git a/src/main/feature.ts b/src/main/feature.ts index b323cb65a6..f278328092 100644 --- a/src/main/feature.ts +++ b/src/main/feature.ts @@ -2,7 +2,7 @@ import fs from "fs"; import path from "path" import hb from "handlebars" import { ResourceApplier } from "./resource-applier" -import { KubeConfig, CoreV1Api, Watch } from "@kubernetes/client-node" +import { CoreV1Api, KubeConfig, Watch } from "@kubernetes/client-node" import { Cluster } from "./cluster"; import logger from "./logger"; @@ -23,75 +23,61 @@ export interface FeatureStatus { export abstract class Feature { name: string; - config: any; latestVersion: string; - constructor(config: any) { - if(config) this.config = config; - } - - // TODO Return types for these? - async install(cluster: Cluster): Promise { - return new Promise((resolve, reject) => { - // Read and process yamls through handlebar - const resources = this.renderTemplates(); - - // Apply processed manifests - cluster.contextHandler.withTemporaryKubeconfig(async (kubeconfigPath) => { - const resourceApplier = new ResourceApplier(cluster, kubeconfigPath) - try { - await resourceApplier.kubectlApplyAll(resources) - resolve(true) - } catch(error) { - reject(error) - } - }); - }); - } - abstract async upgrade(cluster: Cluster): Promise; abstract async uninstall(cluster: Cluster): Promise; abstract async featureStatus(kc: KubeConfig): Promise; + constructor(public config: any) { + } + + async install(cluster: Cluster): Promise { + const resources = this.renderTemplates(); + try { + await new ResourceApplier(cluster).kubectlApplyAll(resources); + return true; + } catch (err) { + logger.error("Installing feature error", { err, cluster }); + return false + } + } + protected async deleteNamespace(kc: KubeConfig, name: string) { return new Promise(async (resolve, reject) => { const client = kc.makeApiClient(CoreV1Api) const result = await client.deleteNamespace("lens-metrics", 'false', undefined, undefined, undefined, "Foreground"); const nsVersion = result.body.metadata.resourceVersion; const nsWatch = new Watch(kc); - const req = await nsWatch.watch('/api/v1/namespaces', {resourceVersion: nsVersion, fieldSelector: "metadata.name=lens-metrics"}, - (type, obj) => { - if(type === 'DELETED') { + const query: Record = { + resourceVersion: nsVersion, + fieldSelector: "metadata.name=lens-metrics", + } + const req = await nsWatch.watch('/api/v1/namespaces', query, + (phase, obj) => { + if (phase === 'DELETED') { logger.debug(`namespace ${name} finally gone`) req.abort(); resolve() } }, - (err) => { - if(err) { - reject(err) - } + (err?: any) => { + if (err) reject(err); }); }); } protected renderTemplates(): string[] { - console.log("starting to render resources..."); const resources: string[] = []; - fs.readdirSync(this.manifestPath()).forEach((f) => { - const file = path.join(this.manifestPath(), f); - console.log("processing file:", file) + fs.readdirSync(this.manifestPath()).forEach(filename => { + const file = path.join(this.manifestPath(), filename); const raw = fs.readFileSync(file); - console.log("raw file loaded"); - if(f.endsWith('.hb')) { - console.log("processing HB template"); + if (filename.endsWith('.hb')) { const template = hb.compile(raw.toString()); resources.push(template(this.config)); - console.log("HB template done"); } else { - console.log("using as raw, no HB detected"); resources.push(raw.toString()); } }); @@ -101,7 +87,7 @@ export abstract class Feature { protected manifestPath() { const devPath = path.join(__dirname, "..", 'src/features', this.name); - if(fs.existsSync(devPath)) { + if (fs.existsSync(devPath)) { return devPath; } return path.join(__dirname, "..", 'features', this.name); diff --git a/src/main/helm-release-manager.ts b/src/main/helm-release-manager.ts index e1e9137f3f..75251f6c6f 100644 --- a/src/main/helm-release-manager.ts +++ b/src/main/helm-release-manager.ts @@ -99,7 +99,7 @@ export class HelmReleaseManager { protected async getResources(name: string, namespace: string, cluster: Cluster) { const helm = await helmCli.binaryPath() - const kubectl = await cluster.kubeCtl.kubectlPath() + const kubectl = await cluster.kubeCtl.getPath() const pathToKubeconfig = cluster.proxyKubeconfigPath() const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch((error) => { return { stdout: JSON.stringify({items: []})} diff --git a/src/main/k8s.ts b/src/main/k8s.ts index ba65e22bd7..1e4fefff8d 100644 --- a/src/main/k8s.ts +++ b/src/main/k8s.ts @@ -11,7 +11,7 @@ function resolveTilde(filePath: string) { return filePath; } -export function loadKubeConfig(pathOrContent?: string): KubeConfig { +export function loadConfig(pathOrContent?: string): KubeConfig { const kc = new KubeConfig(); if (path.isAbsolute(pathOrContent)) { kc.loadFromFile(resolveTilde(pathOrContent)); @@ -30,7 +30,7 @@ export function loadKubeConfig(pathOrContent?: string): KubeConfig { */ export function validateConfig(config: KubeConfig | string): KubeConfig { if (typeof config == "string") { - config = loadKubeConfig(config); + config = loadConfig(config); } logger.debug(`validating kube config: ${JSON.stringify(config)}`) if (!config.users || config.users.length == 0) { @@ -66,17 +66,6 @@ export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] { return configs; } -/** - * Loads KubeConfig from a yaml and breaks it into several configs. Each context per KubeConfig object - * - * @param configPath path to kube config yaml file - */ -export function loadAndSplitConfig(configPath: string): KubeConfig[] { - const allConfigs = new KubeConfig(); - allConfigs.loadFromFile(configPath); - return splitConfig(allConfigs); -} - export function dumpConfigYaml(kc: KubeConfig): string { const config = { apiVersion: "v1", @@ -122,8 +111,7 @@ export function dumpConfigYaml(kc: KubeConfig): string { }) } - console.log("dumping kc:", config); - + // logger.info("Dumping KubeConfig:", config); // skipInvalid: true makes dump ignore undefined values return yaml.safeDump(config, { skipInvalid: true }); } diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index 9a16608201..1be8875040 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -25,7 +25,7 @@ export class KubeAuthProxy { if (this.proxyProcess) { return; } - const proxyBin = await this.kubectl.kubectlPath() + const proxyBin = await this.kubectl.getPath() let args = [ "proxy", "-p", this.port.toString(), diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index f1b5ac6433..6a3f8fcf60 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -1,13 +1,11 @@ import type { Cluster } from "./cluster" import { app } from "electron" import fs from "fs-extra" -import { KubeConfig } from "@kubernetes/client-node" import { randomFileName } from "../common/utils" -import { dumpConfigYaml, loadKubeConfig } from "./k8s" +import { dumpConfigYaml, loadConfig } from "./k8s" import logger from "./logger" export class KubeconfigManager { - public config: KubeConfig; protected configDir = app.getPath("temp") protected tempFile: string @@ -19,16 +17,6 @@ export class KubeconfigManager { return this.tempFile; } - getCurrentClusterServer() { - return this.config.getCurrentCluster().server; - } - - protected loadConfig() { - const { kubeConfigPath, kubeConfig } = this.cluster; - this.config = loadKubeConfig(kubeConfigPath || kubeConfig); - return this.config; - } - /** * 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. @@ -36,12 +24,12 @@ export class KubeconfigManager { protected createTemporaryKubeconfig(): string { fs.ensureDir(this.configDir); const path = `${this.configDir}/${randomFileName("kubeconfig")}`; - const { contextName, kubeAuthProxyUrl } = this.cluster; - const kubeConfig = this.loadConfig(); + const { contextName, contextHandler, kubeConfigPath } = this.cluster; + const kubeConfig = loadConfig(kubeConfigPath); kubeConfig.clusters = [ { name: contextName, - server: kubeAuthProxyUrl, + server: `http://127.0.0.1:${contextHandler.proxyPort}`, // fixme: extract skipTLSVerify: true, } ]; @@ -54,10 +42,11 @@ export class KubeconfigManager { 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)); - logger.info(`Created temp kube-config file for context "${this.cluster.contextName}" at "${path}"`); return path; } diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index e95a7751fc..5e116d6c06 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -98,7 +98,7 @@ export class Kubectl { this.path = path.join(this.dirname, binaryName) } - public async kubectlPath(): Promise { + public async getPath(): Promise { try { await this.ensureKubectl() return this.path diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index b836ece43a..86d2d745f4 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -4,7 +4,7 @@ import { Socket } from "net"; import * as url from "url"; import * as WebSocket from "ws" import { ContextHandler } from "./context-handler"; -import * as shell from "./node-shell-session" +import * as nodeShell from "./node-shell-session" import { ClusterManager } from "./cluster-manager" import { Router } from "./router" import { apiPrefix } from "../common/vars"; @@ -84,12 +84,13 @@ export class LensProxy { if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) { const retryCounterKey = `${req.headers.host}${req.url}` const retryCount = this.retryCounters.get(retryCounterKey) || 0 + const timeoutMs = retryCount * 250 if (retryCount < 20) { logger.debug("Retrying proxy request to url: " + retryCounterKey) setTimeout(() => { this.retryCounters.set(retryCounterKey, retryCount + 1) this.handleRequest(proxy, req, res) - }, (250 * retryCount)) + }, timeoutMs) } } } @@ -102,23 +103,13 @@ export class LensProxy { return proxy; } - protected createWsListener() { + protected createWsListener(): WebSocket.Server { const ws = new WebSocket.Server({ noServer: true }) - ws.on("connection", ((con: WebSocket, req: http.IncomingMessage) => { + return ws.on("connection", (async (socket: WebSocket, req: http.IncomingMessage) => { const cluster = this.clusterManager.getClusterForRequest(req) - const contextHandler = cluster.contextHandler const nodeParam = url.parse(req.url, true).query["node"]?.toString(); - - contextHandler.withTemporaryKubeconfig((kubeconfigPath) => { - return new Promise(async (resolve, reject) => { - const shellSession = await shell.open(con, kubeconfigPath, cluster, nodeParam) - shellSession.on("exit", () => { - resolve(true) - }) - }) - }) - })) - return ws + await nodeShell.open(socket, cluster, nodeParam); + })); } protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise { @@ -146,7 +137,7 @@ export class LensProxy { if (proxyTarget) { proxy.web(req, res, proxyTarget) } else { - this.router.route(cluster, req, res); // todo: handle not-found route when isBoom==true? + this.router.route(cluster, req, res); // todo: handle "not-found" if isBoom==true? } } diff --git a/src/main/node-shell-session.ts b/src/main/node-shell-session.ts index 931355691c..a2b5737ddc 100644 --- a/src/main/node-shell-session.ts +++ b/src/main/node-shell-session.ts @@ -13,15 +13,15 @@ export class NodeShellSession extends ShellSession { protected podId: string protected kc: KubeConfig - constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName: string) { - super(socket, pathToKubeconfig, cluster) + constructor(socket: WebSocket, cluster: Cluster, nodeName: string) { + super(socket, cluster) this.nodeName = nodeName this.podId = `node-shell-${uuid()}` this.kc = cluster.proxyKubeconfig() } public async open() { - const shell = await this.kubectl.kubectlPath() + const shell = await this.kubectl.getPath() let args = [] if (this.createNodeShellPod(this.podId, this.nodeName)) { await this.waitForRunningPod(this.podId).catch((error) => { @@ -133,15 +133,9 @@ export class NodeShellSession extends ShellSession { } } -export async function open(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName?: string): Promise { - return new Promise(async (resolve, reject) => { - let shell = null - if (nodeName) { - shell = new NodeShellSession(socket, pathToKubeconfig, cluster, nodeName) - } else { - shell = new ShellSession(socket, pathToKubeconfig, cluster) - } - shell.open() - resolve(shell) - }) +export async function open(socket: WebSocket, cluster: Cluster, nodeName?: string): Promise { + if (nodeName) { + return new NodeShellSession(socket, cluster, nodeName) + } + return new ShellSession(socket, cluster); } diff --git a/src/main/port.ts b/src/main/port.ts index bd8b49b94a..0669187f65 100644 --- a/src/main/port.ts +++ b/src/main/port.ts @@ -1,6 +1,8 @@ import logger from "./logger" import { createServer, AddressInfo } from "net" +// todo: replace with https://github.com/http-party/node-portfinder ? + const getNextAvailablePort = () => { logger.debug("getNextAvailablePort() start") const server = createServer() diff --git a/src/main/resource-applier-api.ts b/src/main/resource-applier-api.ts index 04fa9f117a..b611fff8bf 100644 --- a/src/main/resource-applier-api.ts +++ b/src/main/resource-applier-api.ts @@ -1,14 +1,14 @@ import { LensApiRequest } from "./router" -import * as resourceApplier from "./resource-applier" +import { ResourceApplier } from "./resource-applier" import { LensApi } from "./lens-api" class ResourceApplierApi extends LensApi { public async applyResource(request: LensApiRequest) { const { response, cluster, payload } = request try { - const resource = await resourceApplier.apply(cluster, cluster.proxyKubeconfigPath(), payload) + const resource = await new ResourceApplier(cluster).apply(payload); this.respondJson(response, [resource], 200) - } catch(error) { + } catch (error) { this.respondText(response, error, 422) } } diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index eb7b2b5620..b7d82308ac 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -1,47 +1,31 @@ +import type { Cluster } from "./cluster"; +import { KubernetesObject } from "@kubernetes/client-node" import { exec } from "child_process"; import fs from "fs"; import * as yaml from "js-yaml"; import path from "path"; import * as tempy from "tempy"; import logger from "./logger" -import { Cluster } from "./cluster"; import { tracker } from "../common/tracker"; - -type KubeObject = { - status: {}; - metadata?: { - resourceVersion: number; - annotations?: { - "kubectl.kubernetes.io/last-applied-configuration": string; - }; - }; -} +import { cloneJsonObject } from "../common/utils"; export class ResourceApplier { - protected kubeconfigPath: string; - protected cluster: Cluster - - constructor(cluster: Cluster, pathToKubeconfig: string) { - this.kubeconfigPath = pathToKubeconfig - this.cluster = cluster + constructor(protected cluster: Cluster) { } - public async apply(resource: any): Promise { - this.sanitizeObject((resource as KubeObject)) - try { - tracker.event("resource", "apply") - return await this.kubectlApply(yaml.safeDump(resource)) - } catch(error) { - throw (error) - } + async apply(resource: KubernetesObject | any): Promise { + resource = this.sanitizeObject(resource); + tracker.event("resource", "apply") + return await this.kubectlApply(yaml.safeDump(resource)); } protected async kubectlApply(content: string): Promise { - const kubectl = await this.cluster.kubeCtl.kubectlPath() + const { kubeCtl, kubeConfigPath } = this.cluster; + const kubectlPath = await kubeCtl.getPath() return new Promise((resolve, reject) => { - const fileName = tempy.file({name: "resource.yaml"}) + const fileName = tempy.file({ name: "resource.yaml" }) fs.writeFileSync(fileName, content) - const cmd = `"${kubectl}" apply --kubeconfig ${this.kubeconfigPath} -o json -f ${fileName}` + const cmd = `"${kubectlPath}" apply --kubeconfig ${kubeConfigPath} -o json -f ${fileName}` logger.debug("shooting manifests with: " + cmd); const execEnv: NodeJS.ProcessEnv = Object.assign({}, process.env) const httpsProxy = this.cluster.preferences?.httpsProxy @@ -62,17 +46,18 @@ export class ResourceApplier { } public async kubectlApplyAll(resources: string[]): Promise { - const kubectl = await this.cluster.kubeCtl.kubectlPath() - return new Promise((resolve, reject) => { + const { kubeCtl, kubeConfigPath } = this.cluster; + const kubectlPath = await kubeCtl.getPath() + return new Promise((resolve, reject) => { const tmpDir = tempy.directory() // Dump each resource into tmpDir - for (const i in resources) { - fs.writeFileSync(path.join(tmpDir, `${i}.yaml`), resources[i]) - } - const cmd = `"${kubectl}" apply --kubeconfig ${this.kubeconfigPath} -o json -f ${tmpDir}` + resources.forEach((resource, index) => { + fs.writeFileSync(path.join(tmpDir, `${index}.yaml`), resource); + }) + const cmd = `"${kubectlPath}" apply --kubeconfig ${kubeConfigPath} -o json -f ${tmpDir}` console.log("shooting manifests with:", cmd); exec(cmd, (error, stdout, stderr) => { - if(error) { + if (error) { reject("Error applying manifests:" + error); } if (stderr != "") { @@ -84,18 +69,14 @@ export class ResourceApplier { }) } - protected sanitizeObject(resource: KubeObject) { - delete resource['status'] - if (resource['metadata']) { - if (resource['metadata']['annotations'] && resource['metadata']['annotations']['kubectl.kubernetes.io/last-applied-configuration']) { - delete resource['metadata']['annotations']['kubectl.kubernetes.io/last-applied-configuration'] - } - delete resource['metadata']['resourceVersion'] + protected sanitizeObject(resource: KubernetesObject | any) { + resource = cloneJsonObject(resource); + delete resource.status; + delete resource.metadata?.resourceVersion; + const annotations = resource.metadata?.annotations; + if (annotations) { + delete annotations['kubectl.kubernetes.io/last-applied-configuration']; } + return resource; } } - -export async function apply(cluster: Cluster, pathToKubeconfig: string, resource: any) { - const resourceApplier = new ResourceApplier(cluster, pathToKubeconfig) - return await resourceApplier.apply(resource) -} diff --git a/src/main/routes/metrics-route.ts b/src/main/routes/metrics-route.ts index 2005ef9fc2..867baf6c86 100644 --- a/src/main/routes/metrics-route.ts +++ b/src/main/routes/metrics-route.ts @@ -2,6 +2,7 @@ import { LensApiRequest } from "../router" import { LensApi } from "../lens-api" import requestPromise from "request-promise-native" import { PrometheusClusterQuery, PrometheusIngressQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusProvider, PrometheusPvcQuery, PrometheusQueryOpts } from "../prometheus/provider-registry" +import { apiPrefix } from "../../common/vars"; export type IMetricsQuery = string | string[] | { [metricName: string]: string; @@ -11,6 +12,7 @@ class MetricsRoute extends LensApi { public async routeMetrics(request: LensApiRequest) { const { response, cluster } = request + const serverUrl = `http://127.0.0.1:${cluster.port}${apiPrefix.KUBE_BASE}` // fixme: extract const query: IMetricsQuery = request.payload; const headers: Record = { "Host": `${cluster.id}.localhost:${cluster.port}`, @@ -25,7 +27,7 @@ class MetricsRoute extends LensApi { let prometheusProvider: PrometheusProvider try { const prometheusPath = await cluster.contextHandler.getPrometheusPath() - metricsUrl = `${cluster.kubeAuthProxyUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range` + metricsUrl = `${serverUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range` prometheusProvider = await cluster.contextHandler.getPrometheusProvider() } catch { this.respondJson(response, {}) diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts index 27b1158700..272e8ddfa0 100644 --- a/src/main/routes/port-forward-route.ts +++ b/src/main/routes/port-forward-route.ts @@ -37,7 +37,7 @@ class PortForward { public async start() { this.localPort = await getFreePort() - const kubectlBin = await bundledKubectl.kubectlPath() + const kubectlBin = await bundledKubectl.getPath() const args = [ "--kubeconfig", this.kubeConfig, "port-forward", diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index 7730ede3a2..b8c84135a0 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -25,10 +25,10 @@ export class ShellSession extends EventEmitter { protected running = false; protected clusterId: string; - constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster) { + constructor(socket: WebSocket, cluster: Cluster) { super() this.websocket = socket - this.kubeconfigPath = pathToKubeconfig + this.kubeconfigPath = cluster.kubeConfigPath this.kubectl = new Kubectl(cluster.version) this.preferences = cluster.preferences || {} this.clusterId = cluster.id diff --git a/src/migrations/cluster-store/3.6.0-beta.1.ts b/src/migrations/cluster-store/3.6.0-beta.1.ts index 8b9e24c804..81b759ec6d 100644 --- a/src/migrations/cluster-store/3.6.0-beta.1.ts +++ b/src/migrations/cluster-store/3.6.0-beta.1.ts @@ -4,40 +4,33 @@ import path from "path" import { app, remote } from "electron" import { migration } from "../migration-wrapper"; import { ensureDirSync } from "fs-extra" -import { KubeConfig } from "@kubernetes/client-node"; import { writeEmbeddedKubeConfig } from "../../common/utils/kubeconfig" import { ClusterModel } from "../../common/cluster-store"; export default migration({ version: "3.6.0-beta.1", run(store, log: (...args: any[]) => void) { - const migratingClusters: ClusterModel[] = [] - + const migratedClusters: ClusterModel[] = [] + const storedClusters: ClusterModel[] = store.get("clusters"); const kubeConfigBase = path.join((app || remote.app).getPath("userData"), "kubeconfigs") - ensureDirSync(kubeConfigBase) - const storedClusters: ClusterModel[] = store.get("clusters") - if (!storedClusters) return + + if (!storedClusters) return; + ensureDirSync(kubeConfigBase); log("Number of clusters to migrate: ", storedClusters.length) for (const cluster of storedClusters) { try { // take the embedded kubeconfig and dump it into a file cluster.kubeConfigPath = writeEmbeddedKubeConfig(cluster.id, cluster.kubeConfig) - - const kc = new KubeConfig() - kc.loadFromFile(cluster.kubeConfigPath) - cluster.contextName = kc.getCurrentContext() - - delete cluster.kubeConfig - migratingClusters.push(cluster) + migratedClusters.push(cluster) } catch (error) { log(`Failed to migrate Kubeconfig for cluster "${cluster.id}"`, error) } } // "overwrite" the cluster configs - if (migratingClusters.length > 0) { - store.set("clusters", migratingClusters) + if (migratedClusters.length > 0) { + store.set("clusters", migratedClusters) } } })