From bde60b1625270f7512e1fe59d30fce4e4e9a3a20 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 7 Jul 2020 15:10:43 +0300 Subject: [PATCH] added common/ipc-helpers Signed-off-by: Roman --- package.json | 1 + src/common/base-store.ts | 5 ++ src/common/ipc-helpers.ts | 39 ++++++++++ src/common/utils/kubeconfig.ts | 2 + src/main/cluster.ts | 8 +-- src/main/context-handler.ts | 72 ++++++++----------- .../MainMenu/AddClusterMenuItem.vue | 2 - yarn.lock | 5 ++ 8 files changed, 83 insertions(+), 51 deletions(-) create mode 100644 src/common/ipc-helpers.ts diff --git a/package.json b/package.json index 5afbc706b8..7fcc3c230f 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,7 @@ "fs-extra": "^9.0.1", "handlebars": "^4.7.6", "http-proxy": "^1.18.1", + "immer": "^7.0.5", "js-yaml": "^3.14.0", "jsonpath": "^1.0.2", "lodash": "^4.17.15", diff --git a/src/common/base-store.ts b/src/common/base-store.ts index cdb1f3973a..25846e42c7 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -1,6 +1,7 @@ import path from "path" import Config from "conf" import { Options as ConfOptions } from "conf/dist/source/types" +import produce from "immer"; import { app, remote } from "electron" import { observable, reaction, toJS, when } from "mobx"; import Singleton from "./utils/singleton"; @@ -115,6 +116,10 @@ export class BaseStore extends Singleton { Object.assign(this.data, data); } + merge(updater: (modelDraft: T) => void) { + this.data = produce(this.data, updater); + } + toJSON(): T { return toJS(this.data, { recurseEverything: true, diff --git a/src/common/ipc-helpers.ts b/src/common/ipc-helpers.ts new file mode 100644 index 0000000000..b48e6e7f70 --- /dev/null +++ b/src/common/ipc-helpers.ts @@ -0,0 +1,39 @@ +// Inter-protocol communications (main <-> renderer) +// https://www.electronjs.org/docs/api/ipc-main +// https://www.electronjs.org/docs/api/ipc-renderer + +import { ipcMain, ipcRenderer } from "electron" +import logger from "../main/logger"; + +export interface IpcOptions { + timeout?: number; +} + +export async function sendMessage(channel: string, ...args: any[]) { + logger.debug(`[IPC]: invoke "${channel}" with arguments`, args); + return ipcRenderer.invoke(channel, ...args); +} + +// todo: maybe spawn callback in separate thread/worker +export function onMessage(channel: string, callback: (...args: any[]) => T, options: IpcOptions = {}) { + const { timeout = 0 } = options; + ipcMain.handle(channel, async (event, ...args: any[]) => { + logger.debug(`[IPC]: handle "${channel}"`, event, args); + return new Promise(async (resolve, reject) => { + let timerId; + if (timeout) { + timerId = setTimeout(() => { + const timeoutError = new Error("[IPC]: response timeout"); + reject(timeoutError); + }, timeout); + } + try { + const result = await callback(...args); + clearTimeout(timerId); + return result; + } catch (err) { + logger.debug(`[IPC]: handling "${channel}" error`, err); + } + }) + }) +} diff --git a/src/common/utils/kubeconfig.ts b/src/common/utils/kubeconfig.ts index 1f6cf4a874..4e5e314524 100644 --- a/src/common/utils/kubeconfig.ts +++ b/src/common/utils/kubeconfig.ts @@ -2,6 +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/ export function writeEmbeddedKubeConfig(clusterId: string, kubeConfig: string): string { // This can be called from main & renderer diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 725b9ae40a..13acf7bd51 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -7,7 +7,6 @@ import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from import * as fm from "./feature-manager"; import { Kubectl } from "./kubectl"; import { KubeconfigManager } from "./kubeconfig-manager" -import { PromiseIpc } from "electron-promise-ipc" import request from "request-promise-native" import { apiPrefix } from "../common/vars"; import type { ClusterInfo } from "../renderer/_vue/store/modules/clusters"; @@ -40,12 +39,10 @@ export class Cluster implements ClusterModel { public preferences: ClusterPreferences; protected eventPoller: NodeJS.Timeout; - protected promiseIpc = new PromiseIpc({ timeout: 2000 }) - protected kubeconfigManager: KubeconfigManager; - constructor(jsonModel: ClusterModel) { - if (jsonModel) Object.assign(this, jsonModel) + constructor(model: ClusterModel) { + if (model) Object.assign(this, model) if (!this.preferences) this.preferences = {} } @@ -64,7 +61,6 @@ export class Cluster implements ClusterModel { this.contextHandler = new ContextHandler(kc, this) await this.contextHandler.init() // So we get the proxy port reserved this.kubeconfigManager = new KubeconfigManager(this) - this.url = this.contextHandler.url } diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index 9b7227d53a..8feec50803 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -1,5 +1,4 @@ import { CoreV1Api, KubeConfig } from "@kubernetes/client-node" -import http from "http" import { ServerOptions } from "http-proxy" import * as url from "url" import logger from "./logger" @@ -7,20 +6,20 @@ import { getFreePort } from "./port" import { KubeAuthProxy } from "./kube-auth-proxy" import { Cluster } from "./cluster" import { prometheusProviders } from "../common/prometheus-providers" -import type { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry" +import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry" import type { ClusterPreferences } from "../common/cluster-store"; export class ContextHandler { - public contextName: string - public id: string public url: string - public clusterUrl: url.UrlWithStringQuery - public proxyServer: KubeAuthProxy public proxyPort: number - public certData: string - public authCertData: string - public cluster: Cluster + public contextName: string + protected id: string + protected clusterUrl: url.UrlWithStringQuery + protected proxyServer: KubeAuthProxy + protected certData: string + protected authCertData: string + protected cluster: Cluster protected apiTarget: ServerOptions protected proxyTarget: ServerOptions protected clientCert: string @@ -34,7 +33,6 @@ export class ContextHandler { constructor(kc: KubeConfig, cluster: Cluster) { this.id = cluster.id - this.cluster = cluster this.clusterUrl = url.parse(cluster.apiUrl) this.contextName = cluster.contextName; @@ -49,26 +47,19 @@ export class ContextHandler { await this.resolveProxyPort() } - public setClusterPreferences(clusterPreferences?: ClusterPreferences) { - this.prometheusProvider = clusterPreferences.prometheusProvider?.type + public setClusterPreferences(preferences?: ClusterPreferences) { + this.clusterName = preferences?.clusterName || this.contextName; + this.prometheusProvider = preferences.prometheusProvider?.type; + this.prometheusPath = null; - if (clusterPreferences && clusterPreferences.prometheus) { - const prom = clusterPreferences.prometheus - this.prometheusPath = `${prom.namespace}/services/${prom.service}:${prom.port}` - } - else { - this.prometheusPath = null - } - if (clusterPreferences && clusterPreferences.clusterName) { - this.clusterName = clusterPreferences.clusterName; - } - else { - this.clusterName = this.contextName; + if (preferences?.prometheus) { + const { namespace, service, port } = preferences.prometheus + this.prometheusPath = `${namespace}/services/${service}:${port}` } } protected async resolvePrometheusPath(): Promise { - const {service, namespace, port} = await this.getPrometheusService() + const { service, namespace, port } = await this.getPrometheusService() return `${namespace}/services/${service}:${port}` } @@ -91,8 +82,7 @@ export class ContextHandler { const service = resolvedPrometheusServices.filter(n => n)[0] if (service) { return service - } - else { + } else { return { id: "lens", namespace: "lens-metrics", @@ -103,11 +93,10 @@ export class ContextHandler { } public async getPrometheusPath(): Promise { - if (this.prometheusPath) return this.prometheusPath - - this.prometheusPath = await this.resolvePrometheusPath() - - return this.prometheusPath + if (!this.prometheusPath) { + this.prometheusPath = await this.resolvePrometheusPath() + } + return this.prometheusPath; } public async getApiTarget(isWatchRequest = false): Promise { @@ -139,18 +128,15 @@ export class ContextHandler { } protected async resolveProxyPort(): Promise { - if (this.proxyPort) return this.proxyPort - - let serverPort: number = null - try { - serverPort = await getFreePort() - } catch (error) { - logger.error(error) - throw(error) + if (!this.proxyPort) { + try { + this.proxyPort = await getFreePort() + } catch (error) { + logger.error(error) + throw(error) + } } - this.proxyPort = serverPort - - return serverPort + return this.proxyPort } public async withTemporaryKubeconfig(callback: (kubeconfig: string) => Promise) { diff --git a/src/renderer/_vue/components/MainMenu/AddClusterMenuItem.vue b/src/renderer/_vue/components/MainMenu/AddClusterMenuItem.vue index 819817b329..4237a0124c 100644 --- a/src/renderer/_vue/components/MainMenu/AddClusterMenuItem.vue +++ b/src/renderer/_vue/components/MainMenu/AddClusterMenuItem.vue @@ -18,7 +18,6 @@