From 35f081eaf487a878e735f255d38087554acc894b Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 11 Jul 2020 19:07:06 +0300 Subject: [PATCH] refactoring / fixes Signed-off-by: Roman --- build/download_helm.ts | 2 +- src/common/vars.ts | 2 +- src/features/metrics.ts | 6 +- src/features/user-mode.ts | 2 +- src/main/cluster-manager.ts | 10 ++- src/main/cluster.ts | 71 +++++++++---------- src/main/context-handler.ts | 21 +++--- src/main/feature-manager.ts | 2 +- src/main/{ => helm}/helm-chart-manager.ts | 4 +- src/main/{ => helm}/helm-cli.ts | 6 +- src/main/{ => helm}/helm-release-manager.ts | 12 ++-- src/main/{ => helm}/helm-repo-manager.ts | 7 +- src/main/{ => helm}/helm-service.ts | 41 +++++------ src/main/kubeconfig-manager.ts | 8 ++- src/main/kubectl.ts | 2 +- src/main/lens-proxy.ts | 35 ++++----- src/main/node-shell-session.ts | 2 +- src/main/router.ts | 28 ++++---- src/main/routes/config-route.ts | 4 +- .../{helm-api.ts => routes/helm-route.ts} | 12 ++-- src/main/routes/index.ts | 4 +- src/main/routes/kubeconfig-route.ts | 2 +- src/main/routes/port-forward-route.ts | 2 +- .../resource-applier-route.ts} | 10 +-- src/main/routes/watch-route.ts | 4 +- src/main/shell-session.ts | 2 +- src/renderer/_vue/store/modules/helm-repos.ts | 2 +- 27 files changed, 148 insertions(+), 155 deletions(-) rename src/main/{ => helm}/helm-chart-manager.ts (96%) rename src/main/{ => helm}/helm-cli.ts (86%) rename src/main/{ => helm}/helm-release-manager.ts (92%) rename src/main/{ => helm}/helm-repo-manager.ts (97%) rename src/main/{ => helm}/helm-service.ts (62%) rename src/main/{helm-api.ts => routes/helm-route.ts} (93%) rename src/main/{resource-applier-api.ts => routes/resource-applier-route.ts} (56%) diff --git a/build/download_helm.ts b/build/download_helm.ts index 2f81b14583..9dddf7fcdc 100644 --- a/build/download_helm.ts +++ b/build/download_helm.ts @@ -1,3 +1,3 @@ -import { helmCli } from "../src/main/helm-cli" +import { helmCli } from "../src/main/helm/helm-cli" helmCli.ensureBinary() diff --git a/src/common/vars.ts b/src/common/vars.ts index bbc5c17072..6ad061bf3a 100644 --- a/src/common/vars.ts +++ b/src/common/vars.ts @@ -23,7 +23,7 @@ export const htmlTemplate = path.resolve(rendererDir, "template.html"); export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss"); // Apis -export const apiPrefix = "/api-local" // local router apis +export const apiPrefix = "/api" // local router apis export const apiKubePrefix = "/api-kube" // k8s cluster apis // Links diff --git a/src/features/metrics.ts b/src/features/metrics.ts index 98e71e50d2..a8b556b7a6 100644 --- a/src/features/metrics.ts +++ b/src/features/metrics.ts @@ -53,7 +53,7 @@ export class MetricsFeature extends Feature { async install(cluster: Cluster): Promise { // Check if there are storageclasses - const storageClient = cluster.proxyKubeconfig().makeApiClient(k8s.StorageV1Api) + const storageClient = cluster.getProxyKubeconfig().makeApiClient(k8s.StorageV1Api) const scs = await storageClient.listStorageClass(); scs.body.items.forEach(sc => { if(sc.metadata.annotations && @@ -93,9 +93,9 @@ export class MetricsFeature extends Feature { async uninstall(cluster: Cluster): Promise { return new Promise(async (resolve, reject) => { - const rbacClient = cluster.proxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) + const rbacClient = cluster.getProxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) try { - await this.deleteNamespace(cluster.proxyKubeconfig(), "lens-metrics") + await this.deleteNamespace(cluster.getProxyKubeconfig(), "lens-metrics") await rbacClient.deleteClusterRole("lens-prometheus"); await rbacClient.deleteClusterRoleBinding("lens-prometheus"); resolve(true); diff --git a/src/features/user-mode.ts b/src/features/user-mode.ts index d1f1dcf381..a4c731a5ae 100644 --- a/src/features/user-mode.ts +++ b/src/features/user-mode.ts @@ -37,7 +37,7 @@ export class UserModeFeature extends Feature { async uninstall(cluster: Cluster): Promise { return new Promise(async (resolve, reject) => { - const rbacClient = cluster.proxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) + const rbacClient = cluster.getProxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) try { await rbacClient.deleteClusterRole("lens-user"); await rbacClient.deleteClusterRoleBinding("lens-user"); diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index a49eccecc8..e961719300 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -25,13 +25,11 @@ export class ClusterManager { return path.join(app.getPath("userData"), "icons"); } - constructor(public readonly proxyPort: number) { + constructor(public readonly port: number) { // auto-init clusters autorun(() => { const freshClusters = clusterStore.clustersList.filter(cluster => !cluster.initialized); - freshClusters.forEach(cluster => { - cluster.init().then(() => cluster.refreshCluster()); - }); + freshClusters.forEach(cluster => cluster.init(port)); }); // auto-stop removed clusters autorun(() => { @@ -89,7 +87,7 @@ export class ClusterManager { } } - // fixme + // fixme: verify getClusterForRequest(req: http.IncomingMessage): Cluster { let cluster: Cluster = null @@ -154,7 +152,7 @@ export class ClusterManager { } protected async refreshCluster(clusterId: ClusterId) { - await this.getCluster(clusterId)?.refreshCluster(); + await this.getCluster(clusterId)?.refreshStatus(); } static ipcListen(clusterManager: ClusterManager) { diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 1c0c038eb3..5118d7a205 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -1,12 +1,12 @@ 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 { apiKubePrefix } from "../common/vars"; import { ContextHandler } from "./context-handler" import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node" import { Kubectl } from "./kubectl"; import { KubeconfigManager } from "./kubeconfig-manager" -import { getNodeWarningConditions, podHasIssues } from "./k8s" +import { getNodeWarningConditions, loadConfig, podHasIssues } from "./k8s" import { getFeatures, installFeature, uninstallFeature, upgradeFeature } from "./feature-manager"; import request, { RequestPromiseOptions } from "request-promise-native" import logger from "./logger" @@ -40,11 +40,11 @@ export class Cluster implements ClusterModel { @observable contextName: string; @observable workspace: string; @observable kubeConfigPath: string; - @observable port: number; @observable url: string; // cluster-api url - @observable apiUrl: UrlWithStringQuery; // same as url, but parsed - @observable kubeAuthProxyUrl: string; + @observable proxyUrl: string; // lens-proxy url + @observable kubeProxyUrl: string; @observable webContentUrl: string; + @observable proxyPort: number; @observable online: boolean; @observable accessible: boolean; @observable failureReason: string; @@ -68,26 +68,27 @@ export class Cluster implements ClusterModel { } @action - async init() { + async init(proxyPort: number) { try { - // fixme + this.proxyPort = proxyPort; this.contextHandler = new ContextHandler(this); - this.port = await this.contextHandler.ensurePort(); // resolve port before KubeconfigManager - this.kubeAuthProxyUrl = `http://127.0.0.1:${this.port}`; - this.kubeconfigManager = new KubeconfigManager(this); + this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler); - // this.url = this.kubeconfigManager.getCurrentClusterServer(); - // this.apiUrl = url.parse(this.url); - this.webContentUrl = `http://${this.id}.localhost:${this.port}`; + this.url = this.getKubeconfig().getCurrentCluster().server; + this.proxyUrl = `http://localhost:${proxyPort}`; + this.kubeProxyUrl = this.proxyUrl + apiKubePrefix; + this.webContentUrl = `http://${this.id}.localhost:${proxyPort}`; + + await this.refreshStatus(); + this.initialized = true; logger.info(`[CLUSTER]: init success`, { id: this.id, - port: this.port, url: this.url, + proxyUrl: this.proxyUrl, + kubeProxyUrl: this.kubeProxyUrl, webContentUrl: this.webContentUrl, - kubeAuthProxyUrl: this.kubeAuthProxyUrl, }); - this.initialized = true; } catch (err) { logger.error(`[CLUSTER]: init error`, { id: this.id, @@ -102,15 +103,11 @@ export class Cluster implements ClusterModel { this.kubeconfigManager.unlink(); } - // todo: auto-refresh when preferences changed + by timer? @action - async refreshCluster() { - this.contextHandler.setupPrometheus(this.preferences); - - const connectionStatus = await this.getConnectionStatus() - this.accessible = connectionStatus == ClusterStatus.AccessGranted; + async refreshStatus() { + const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; - + this.accessible = connectionStatus == ClusterStatus.AccessGranted; if (this.accessible) { this.distribution = this.detectKubernetesDistribution(this.version) this.features = await getFeatures(this) @@ -122,29 +119,31 @@ export class Cluster implements ClusterModel { this.eventCount = await this.getEventCount(); } - proxyKubeconfigPath() { - return this.kubeconfigManager.getPath() + protected getKubeconfig(): KubeConfig { + return loadConfig(this.kubeConfigPath); } - proxyKubeconfig() { - const kc = new KubeConfig() - kc.loadFromFile(this.proxyKubeconfigPath()) - return kc + getProxyKubeconfig(): KubeConfig { + return loadConfig(this.getProxyKubeconfigPath()); + } + + getProxyKubeconfigPath(): string { + return this.kubeconfigManager.getPath() } async installFeature(name: string, config: any) { await installFeature(name, this, config) - await this.refreshCluster() + await this.refreshStatus() } async upgradeFeature(name: string, config: any) { await upgradeFeature(name, this, config) - await this.refreshCluster() + await this.refreshStatus() } async uninstallFeature(name: string) { await uninstallFeature(name, this) - await this.refreshCluster() + await this.refreshStatus() } getPrometheusApiPrefix() { @@ -152,12 +151,12 @@ export class Cluster implements ClusterModel { } k8sRequest(path: string, options: RequestPromiseOptions = {}) { - return request(this.kubeAuthProxyUrl + path, { + return request(this.kubeProxyUrl + path, { json: true, timeout: 10000, headers: { ...(options.headers || {}), - host: `${this.id}.localhost:${this.port}`, + host: `${this.id}.localhost:${this.proxyPort}`, } }) } @@ -193,7 +192,7 @@ export class Cluster implements ClusterModel { } async canI(resourceAttributes: V1ResourceAttributes): Promise { - const authApi = this.proxyKubeconfig().makeApiClient(AuthorizationV1Api) + const authApi = this.getProxyKubeconfig().makeApiClient(AuthorizationV1Api) try { const accessReview = await authApi.createSelfSubjectAccessReview({ apiVersion: "authorization.k8s.io/v1", @@ -240,7 +239,7 @@ export class Cluster implements ClusterModel { if (!this.isAdmin) { return 0; } - const client = this.proxyKubeconfig().makeApiClient(CoreV1Api); + const client = this.getProxyKubeconfig().makeApiClient(CoreV1Api); try { const response = await client.listEventForAllNamespaces(false, null, null, null, 1000); const uniqEventSources = new Set(); diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index 9f3a5f629e..b7b0690f03 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -2,26 +2,27 @@ import type { PrometheusProvider, PrometheusService } from "./prometheus/provide import type { ClusterPreferences } from "../common/cluster-store"; import type { Cluster } from "./cluster" import type httpProxy from "http-proxy" +import url, { UrlWithStringQuery } from "url"; import { CoreV1Api } from "@kubernetes/client-node" -import { observable } from "mobx"; import { prometheusProviders } from "../common/prometheus-providers" import logger from "./logger" import { getFreePort } from "./port" import { KubeAuthProxy } from "./kube-auth-proxy" export class ContextHandler { - @observable proxyPort: number; - + public proxyPort: number; + public clusterUrl: UrlWithStringQuery; protected proxyServer: KubeAuthProxy protected apiTarget: httpProxy.ServerOptions protected prometheusProvider: string protected prometheusPath: string constructor(protected cluster: Cluster) { - this.setupPrometheus(cluster.preferences) + this.clusterUrl = url.parse(cluster.url); + this.setupPrometheus(cluster.preferences); } - public setupPrometheus(preferences: ClusterPreferences = {}) { + protected setupPrometheus(preferences: ClusterPreferences = {}) { this.prometheusProvider = preferences.prometheusProvider?.type; this.prometheusPath = null; if (preferences.prometheus) { @@ -47,7 +48,7 @@ export class ContextHandler { public async getPrometheusService(): Promise { const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders; const prometheusPromises: Promise[] = providers.map(async (provider: PrometheusProvider): Promise => { - const apiClient = this.cluster.proxyKubeconfig().makeApiClient(CoreV1Api) + const apiClient = this.cluster.getProxyKubeconfig().makeApiClient(CoreV1Api) return await provider.getPrometheusService(apiClient) }) const resolvedPrometheusServices = await Promise.all(prometheusPromises) @@ -79,10 +80,10 @@ export class ContextHandler { return apiTarget } + // fixme: verify public async getApiTargetUrl(): Promise { - const port = await this.ensurePort(); - const { path } = this.cluster.apiUrl; - return `http://127.0.0.1:${port}${path}`; + await this.ensurePort(); + return `http://127.0.0.1:${this.proxyPort}${this.clusterUrl.path}`; } protected async newApiTarget(timeout: number): Promise { @@ -91,7 +92,7 @@ export class ContextHandler { target: await this.getApiTargetUrl(), timeout: timeout, headers: { - "Host": this.cluster.apiUrl.hostname, + "Host": this.clusterUrl.hostname, } } } diff --git a/src/main/feature-manager.ts b/src/main/feature-manager.ts index 6796f5d662..f1e20be07a 100644 --- a/src/main/feature-manager.ts +++ b/src/main/feature-manager.ts @@ -20,7 +20,7 @@ export async function getFeatures(cluster: Cluster): Promise { logger.debug("getting feature status..."); const feature = ALL_FEATURES[key] as Feature; const kc = new KubeConfig() - kc.loadFromFile(cluster.proxyKubeconfigPath()) + kc.loadFromFile(cluster.getProxyKubeconfigPath()) const status = await feature.featureStatus(kc); result[feature.name] = status diff --git a/src/main/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts similarity index 96% rename from src/main/helm-chart-manager.ts rename to src/main/helm/helm-chart-manager.ts index ed2bea5537..e4fe94898e 100644 --- a/src/main/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -1,8 +1,8 @@ import fs from "fs"; import * as yaml from "js-yaml"; import { HelmRepo, HelmRepoManager } from "./helm-repo-manager" -import logger from "./logger"; -import { promiseExec } from "./promise-exec" +import logger from "../logger"; +import { promiseExec } from "../promise-exec" import { helmCli } from "./helm-cli" type CachedYaml = { diff --git a/src/main/helm-cli.ts b/src/main/helm/helm-cli.ts similarity index 86% rename from src/main/helm-cli.ts rename to src/main/helm/helm-cli.ts index 40856b80dc..1484ceacf1 100644 --- a/src/main/helm-cli.ts +++ b/src/main/helm/helm-cli.ts @@ -1,7 +1,7 @@ -import packageInfo from "../../package.json" +import packageInfo from "../../../package.json" import path from "path" -import { LensBinary, LensBinaryOpts } from "./lens-binary" -import { isProduction } from "../common/vars"; +import { LensBinary, LensBinaryOpts } from "../lens-binary" +import { isProduction } from "../../common/vars"; export class HelmCli extends LensBinary { diff --git a/src/main/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts similarity index 92% rename from src/main/helm-release-manager.ts rename to src/main/helm/helm-release-manager.ts index 75251f6c6f..80be023227 100644 --- a/src/main/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -1,10 +1,10 @@ import * as tempy from "tempy"; import fs from "fs"; import * as yaml from "js-yaml"; -import { promiseExec} from "./promise-exec" +import { promiseExec} from "../promise-exec" import { helmCli } from "./helm-cli"; -import { Cluster } from "./cluster"; -import { toCamelCase } from "../common/utils/camelCase"; +import { Cluster } from "../cluster"; +import { toCamelCase } from "../../common/utils/camelCase"; export class HelmReleaseManager { @@ -54,7 +54,7 @@ export class HelmReleaseManager { await fs.promises.writeFile(fileName, yaml.safeDump(values)) try { - const { stdout, stderr } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${cluster.proxyKubeconfigPath()}`).catch((error) => { throw(error.stderr)}) + const { stdout, stderr } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr)}) return { log: stdout, release: this.getRelease(name, namespace, cluster) @@ -66,7 +66,7 @@ export class HelmReleaseManager { public async getRelease(name: string, namespace: string, cluster: Cluster) { const helm = await helmCli.binaryPath() - const {stdout, stderr} = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${cluster.proxyKubeconfigPath()}`).catch((error) => { throw(error.stderr)}) + const {stdout, stderr} = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${cluster.getProxyKubeconfigPath()}`).catch((error) => { throw(error.stderr)}) const release = JSON.parse(stdout) release.resources = await this.getResources(name, namespace, cluster) return release @@ -100,7 +100,7 @@ export class HelmReleaseManager { protected async getResources(name: string, namespace: string, cluster: Cluster) { const helm = await helmCli.binaryPath() const kubectl = await cluster.kubeCtl.getPath() - const pathToKubeconfig = cluster.proxyKubeconfigPath() + const pathToKubeconfig = cluster.getProxyKubeconfigPath() 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/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts similarity index 97% rename from src/main/helm-repo-manager.ts rename to src/main/helm/helm-repo-manager.ts index b1219ffa79..e8fa1f4a34 100644 --- a/src/main/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -1,9 +1,9 @@ import fs from "fs"; -import logger from "./logger"; +import logger from "../logger"; import * as yaml from "js-yaml"; -import { promiseExec } from "./promise-exec"; +import { promiseExec } from "../promise-exec"; import { helmCli } from "./helm-cli"; -import { Singleton } from "../common/utils/singleton"; +import { Singleton } from "../../common/utils/singleton"; export type HelmEnv = Record & { HELM_REPOSITORY_CACHE?: string; @@ -125,7 +125,6 @@ export class HelmRepoManager extends Singleton { logger.error(error) } }) - } public async addRepo(repository: HelmRepo) { diff --git a/src/main/helm-service.ts b/src/main/helm/helm-service.ts similarity index 62% rename from src/main/helm-service.ts rename to src/main/helm/helm-service.ts index ee76e814b8..664a30358c 100644 --- a/src/main/helm-service.ts +++ b/src/main/helm/helm-service.ts @@ -1,13 +1,12 @@ -import { Cluster } from "./cluster"; -import logger from "./logger"; +import { Cluster } from "../cluster"; +import logger from "../logger"; import { repoManager } from "./helm-repo-manager"; import { HelmChartManager } from "./helm-chart-manager"; import { releaseManager } from "./helm-release-manager"; class HelmService { - public async installChart(cluster: Cluster, data: {chart: string; values: {}; name: string; namespace: string; version: string}) { - const installResult = await releaseManager.installChart(data.chart, data.values, data.name, data.namespace, data.version, cluster.proxyKubeconfigPath()) - return installResult + public async installChart(cluster: Cluster, data: { chart: string; values: {}; name: string; namespace: string; version: string }) { + return await releaseManager.installChart(data.chart, data.values, data.name, data.namespace, data.version, cluster.getProxyKubeconfigPath()) } public async listCharts() { @@ -19,7 +18,7 @@ class HelmService { const manager = new HelmChartManager(repo) let entries = await manager.charts() entries = this.excludeDeprecated(entries) - for(const key in entries) { + for (const key in entries) { entries[key] = entries[key][0] } charts[repo.name] = entries @@ -48,50 +47,44 @@ class HelmService { public async listReleases(cluster: Cluster, namespace: string = null) { await repoManager.init() - const releases = await releaseManager.listReleases(cluster.proxyKubeconfigPath(), namespace) - return releases + return await releaseManager.listReleases(cluster.getProxyKubeconfigPath(), namespace) } - public async getRelease(cluster: Cluster, releaseName: string, namespace: string) { + public async getRelease(cluster: Cluster, releaseName: string, namespace: string) { logger.debug("Fetch release") - const release = await releaseManager.getRelease(releaseName, namespace, cluster) - return release + return await releaseManager.getRelease(releaseName, namespace, cluster) } public async getReleaseValues(cluster: Cluster, releaseName: string, namespace: string) { logger.debug("Fetch release values") - const values = await releaseManager.getValues(releaseName, namespace, cluster.proxyKubeconfigPath()) - return values + return await releaseManager.getValues(releaseName, namespace, cluster.getProxyKubeconfigPath()) } public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) { logger.debug("Fetch release history") - const history = await releaseManager.getHistory(releaseName, namespace, cluster.proxyKubeconfigPath()) - return(history) + return await releaseManager.getHistory(releaseName, namespace, cluster.getProxyKubeconfigPath()) } public async deleteRelease(cluster: Cluster, releaseName: string, namespace: string) { logger.debug("Delete release") - const release = await releaseManager.deleteRelease(releaseName, namespace, cluster.proxyKubeconfigPath()) - return release + return await releaseManager.deleteRelease(releaseName, namespace, cluster.getProxyKubeconfigPath()) } - public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: {chart: string; values: {}; version: string}) { + public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) { logger.debug("Upgrade release") - const release = await releaseManager.upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster) - return release + return await releaseManager.upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster) } public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) { logger.debug("Rollback release") - const output = await releaseManager.rollback(releaseName, namespace, revision, cluster.proxyKubeconfigPath()) - return({ message: output }) + const output = await releaseManager.rollback(releaseName, namespace, revision, cluster.getProxyKubeconfigPath()) + return { message: output } } protected excludeDeprecated(entries: any) { - for(const key in entries) { + for (const key in entries) { entries[key] = entries[key].filter((entry: any) => { - if(Array.isArray(entry)) { + if (Array.isArray(entry)) { return entry[0]['deprecated'] != true } return entry["deprecated"] != true diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index 90f71f67c8..c5b0c4547a 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -1,3 +1,4 @@ +import type { ContextHandler } from "./context-handler"; import type { Cluster } from "./cluster" import { app } from "electron" import fs from "fs-extra" @@ -9,11 +10,12 @@ export class KubeconfigManager { protected configDir = app.getPath("temp") protected tempFile: string - constructor(protected cluster: Cluster) { + constructor(protected cluster: Cluster, protected contextHandler: ContextHandler) { this.init(); } protected async init() { + await this.contextHandler.ensurePort(); this.tempFile = await this.createTemporaryKubeconfig(); } @@ -28,12 +30,12 @@ export class KubeconfigManager { protected async createTemporaryKubeconfig(): Promise { fs.ensureDir(this.configDir); const path = `${this.configDir}/${randomFileName("kubeconfig")}`; - const { contextName, contextHandler, kubeConfigPath } = this.cluster; + const { contextName, kubeConfigPath } = this.cluster; const kubeConfig = loadConfig(kubeConfigPath); kubeConfig.clusters = [ { name: contextName, - server: await contextHandler.getApiTargetUrl(), + server: await this.contextHandler.getApiTargetUrl(), skipTLSVerify: true, } ]; diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 5e116d6c06..9372c439a9 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -7,7 +7,7 @@ import logger from "./logger" import { ensureDir, pathExists } from "fs-extra" import { globalRequestOpts } from "../common/request" import * as lockFile from "proper-lockfile" -import { helmCli } from "./helm-cli" +import { helmCli } from "./helm/helm-cli" import { userStore } from "../common/user-store" import { getBundledKubectlVersion} from "../common/utils/app-version" diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 3ed718bb5a..104c0a5ecb 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -26,11 +26,9 @@ export class LensProxy { this.router = new Router(); } - listen(): this { - const proxyServer = this.buildCustomProxy(); - const { proxyPort } = this.clusterManager; - this.proxyServer = proxyServer.listen(proxyPort); - logger.info(`LensProxy server has started http://localhost:${proxyPort}`); + listen(port = this.clusterManager.port): this { + this.proxyServer = this.buildCustomProxy().listen(port); + logger.info(`LensProxy server has started http://localhost:${port}`); return this; } @@ -71,10 +69,10 @@ export class LensProxy { if (req.method !== "GET") { return } - const reqUrl = `${req.headers.host}${req.url}` - if (this.retryCounters.has(reqUrl)) { - logger.debug("Resetting proxy retry cache for url: " + reqUrl) - this.retryCounters.delete(reqUrl) + const reqId = this.getRequestId(req); + if (this.retryCounters.has(reqId)) { + logger.debug(`Resetting proxy retry cache for url: ${reqId}`); + this.retryCounters.delete(reqId) } }) proxy.on("error", (error, req, res, target) => { @@ -84,13 +82,13 @@ export class LensProxy { if (target) { logger.debug("Failed proxy to target: " + JSON.stringify(target, null, 2)); if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) { - const retryCounterKey = `${req.headers.host}${req.url}` - const retryCount = this.retryCounters.get(retryCounterKey) || 0 + const reqId = this.getRequestId(req); + const retryCount = this.retryCounters.get(reqId) || 0 const timeoutMs = retryCount * 250 if (retryCount < 20) { - logger.debug("Retrying proxy request to url: " + retryCounterKey) + logger.debug(`Retrying proxy request to url: ${reqId}`) setTimeout(() => { - this.retryCounters.set(retryCounterKey, retryCount + 1) + this.retryCounters.set(reqId, retryCount + 1) this.handleRequest(proxy, req, res) }, timeoutMs) } @@ -108,13 +106,12 @@ export class LensProxy { protected createWsListener(): WebSocket.Server { const ws = new WebSocket.Server({ noServer: true }) return ws.on("connection", (async (socket: WebSocket, req: http.IncomingMessage) => { - const cluster = this.clusterManager.getClusterForRequest(req) + const cluster = this.clusterManager.getClusterForRequest(req); const nodeParam = url.parse(req.url, true).query["node"]?.toString(); await nodeShell.open(socket, cluster, nodeParam); })); } - // fixme: remove api prefix? protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise { if (req.url.startsWith(apiKubePrefix)) { delete req.headers.authorization @@ -124,11 +121,15 @@ export class LensProxy { } } + protected getRequestId(req: http.IncomingMessage) { + return req.headers.host + req.url; + } + protected async handleRequest(proxy: httpProxy, req: http.IncomingMessage, res: http.ServerResponse) { const cluster = this.clusterManager.getClusterForRequest(req) if (!cluster) { - logger.error("Got request to unknown cluster") - logger.debug(req.headers.host + req.url) + const reqId = this.getRequestId(req); + logger.error("Got request to unknown cluster", { reqId }) res.statusCode = 503 res.end() return diff --git a/src/main/node-shell-session.ts b/src/main/node-shell-session.ts index a2b5737ddc..bc89b5fb25 100644 --- a/src/main/node-shell-session.ts +++ b/src/main/node-shell-session.ts @@ -17,7 +17,7 @@ export class NodeShellSession extends ShellSession { super(socket, cluster) this.nodeName = nodeName this.podId = `node-shell-${uuid()}` - this.kc = cluster.proxyKubeconfig() + this.kc = cluster.getProxyKubeconfig() } public async open() { diff --git a/src/main/router.ts b/src/main/router.ts index f6dd26cd48..65cefe7ac9 100644 --- a/src/main/router.ts +++ b/src/main/router.ts @@ -4,10 +4,8 @@ import http from "http" import path from "path" import { readFile, stat } from "fs-extra" import { Cluster } from "./cluster" -import { helmApi } from "./helm-api" -import { resourceApplierApi } from "./resource-applier-api" import { apiPrefix, appName, outDir } from "../common/vars"; -import { configRoute, kubeconfigRoute, metricsRoute, portForwardRoute, watchRoute } from "./routes"; +import { configRoute, helmRoute, kubeconfigRoute, metricsRoute, portForwardRoute, resourceApplierRoute, watchRoute } from "./routes"; export interface RouterRequestOpts { req: http.IncomingMessage; @@ -129,20 +127,20 @@ export class Router { this.router.add({ method: "post", path: `${apiPrefix}/services/{namespace}/{service}/port-forward/{port}` }, portForwardRoute.routeServicePortForward.bind(portForwardRoute)) // Helm API - this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, helmApi.listCharts.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, helmApi.getChart.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, helmApi.getChartValues.bind(helmApi)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, helmRoute.listCharts.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, helmRoute.getChart.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, helmRoute.getChartValues.bind(helmRoute)) - this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, helmApi.installChart.bind(helmApi)) - this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.updateRelease.bind(helmApi)) - this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, helmApi.rollbackRelease.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, helmApi.listReleases.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.getRelease.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, helmApi.getReleaseValues.bind(helmApi)) - this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, helmApi.getReleaseHistory.bind(helmApi)) - this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.deleteRelease.bind(helmApi)) + this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, helmRoute.installChart.bind(helmRoute)) + this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.updateRelease.bind(helmRoute)) + this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, helmRoute.rollbackRelease.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, helmRoute.listReleases.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.getRelease.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, helmRoute.getReleaseValues.bind(helmRoute)) + this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, helmRoute.getReleaseHistory.bind(helmRoute)) + this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmRoute.deleteRelease.bind(helmRoute)) // Resource Applier API - this.router.add({ method: "post", path: `${apiPrefix}/stack` }, resourceApplierApi.applyResource.bind(resourceApplierApi)) + this.router.add({ method: "post", path: `${apiPrefix}/stack` }, resourceApplierRoute.applyResource.bind(resourceApplierRoute)) } } diff --git a/src/main/routes/config-route.ts b/src/main/routes/config-route.ts index 02911889ae..73382ed95a 100644 --- a/src/main/routes/config-route.ts +++ b/src/main/routes/config-route.ts @@ -45,7 +45,7 @@ const apiResources = [ ] async function getAllowedNamespaces(cluster: Cluster) { - const api = cluster.proxyKubeconfig().makeApiClient(CoreV1Api) + const api = cluster.getProxyKubeconfig().makeApiClient(CoreV1Api) try { const namespaceList = await api.listNamespace() const nsAccessStatuses = await Promise.all( @@ -59,7 +59,7 @@ async function getAllowedNamespaces(cluster: Cluster) { .filter((ns, i) => nsAccessStatuses[i]) .map(ns => ns.metadata.name) } catch(error) { - const ctx = cluster.proxyKubeconfig().getContextObject(cluster.contextName) + const ctx = cluster.getProxyKubeconfig().getContextObject(cluster.contextName) if (ctx.namespace) { return [ctx.namespace] } diff --git a/src/main/helm-api.ts b/src/main/routes/helm-route.ts similarity index 93% rename from src/main/helm-api.ts rename to src/main/routes/helm-route.ts index 266d8b0dd6..0ffd7252d5 100644 --- a/src/main/helm-api.ts +++ b/src/main/routes/helm-route.ts @@ -1,9 +1,9 @@ -import { LensApiRequest } from "./router" -import { helmService } from "./helm-service" -import { LensApi } from "./lens-api" -import logger from "./logger" +import { LensApiRequest } from "../router" +import { helmService } from "../helm/helm-service" +import { LensApi } from "../lens-api" +import logger from "../logger" -class HelmApi extends LensApi { +class HelmApiRoute extends LensApi { public async listCharts(request: LensApiRequest) { const { response } = request const charts = await helmService.listCharts() @@ -111,4 +111,4 @@ class HelmApi extends LensApi { } } -export const helmApi = new HelmApi() +export const helmRoute = new HelmApiRoute() diff --git a/src/main/routes/index.ts b/src/main/routes/index.ts index 5e0a786974..3398fe11d4 100644 --- a/src/main/routes/index.ts +++ b/src/main/routes/index.ts @@ -2,4 +2,6 @@ export * from "./config-route" export * from "./kubeconfig-route" export * from "./metrics-route" export * from "./port-forward-route" -export * from "./watch-route" \ No newline at end of file +export * from "./watch-route" +export * from "./helm-route" +export * from "./resource-applier-route" diff --git a/src/main/routes/kubeconfig-route.ts b/src/main/routes/kubeconfig-route.ts index 54255b1cb8..08a67c13f7 100644 --- a/src/main/routes/kubeconfig-route.ts +++ b/src/main/routes/kubeconfig-route.ts @@ -44,7 +44,7 @@ class KubeconfigRoute extends LensApi { public async routeServiceAccountRoute(request: LensApiRequest) { const { params, response, cluster} = request - const client = cluster.proxyKubeconfig().makeApiClient(CoreV1Api); + const client = cluster.getProxyKubeconfig().makeApiClient(CoreV1Api); const secretList = await client.listNamespacedSecret(params.namespace) const secret = secretList.body.items.find(secret => { const { annotations } = secret.metadata; diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts index 272e8ddfa0..a5fe683cc2 100644 --- a/src/main/routes/port-forward-route.ts +++ b/src/main/routes/port-forward-route.ts @@ -87,7 +87,7 @@ class PortForwardRoute extends LensApi { namespace: params.namespace, name: params.service, port: params.port, - kubeConfig: cluster.proxyKubeconfigPath() + kubeConfig: cluster.getProxyKubeconfigPath() }) const started = await portForward.start() if (!started) { diff --git a/src/main/resource-applier-api.ts b/src/main/routes/resource-applier-route.ts similarity index 56% rename from src/main/resource-applier-api.ts rename to src/main/routes/resource-applier-route.ts index b611fff8bf..56125af8f3 100644 --- a/src/main/resource-applier-api.ts +++ b/src/main/routes/resource-applier-route.ts @@ -1,8 +1,8 @@ -import { LensApiRequest } from "./router" -import { ResourceApplier } from "./resource-applier" -import { LensApi } from "./lens-api" +import { LensApiRequest } from "../router" +import { LensApi } from "../lens-api" +import { ResourceApplier } from "../resource-applier" -class ResourceApplierApi extends LensApi { +class ResourceApplierApiRoute extends LensApi { public async applyResource(request: LensApiRequest) { const { response, cluster, payload } = request try { @@ -14,4 +14,4 @@ class ResourceApplierApi extends LensApi { } } -export const resourceApplierApi = new ResourceApplierApi() +export const resourceApplierRoute = new ResourceApplierApiRoute() diff --git a/src/main/routes/watch-route.ts b/src/main/routes/watch-route.ts index 512e38530f..d88276eaac 100644 --- a/src/main/routes/watch-route.ts +++ b/src/main/routes/watch-route.ts @@ -87,10 +87,10 @@ class WatchRoute extends LensApi { response.setHeader("Content-Type", "text/event-stream") response.setHeader("Cache-Control", "no-cache") response.setHeader("Connection", "keep-alive") - logger.debug("watch using kubeconfig:" + JSON.stringify(cluster.proxyKubeconfig(), null, 2)) + logger.debug("watch using kubeconfig:" + JSON.stringify(cluster.getProxyKubeconfig(), null, 2)) apis.forEach(apiUrl => { - const watcher = new ApiWatcher(apiUrl, cluster.proxyKubeconfig(), response) + const watcher = new ApiWatcher(apiUrl, cluster.getProxyKubeconfig(), response) watcher.start() watchers.push(watcher) }) diff --git a/src/main/shell-session.ts b/src/main/shell-session.ts index b8c84135a0..4b82b2d77d 100644 --- a/src/main/shell-session.ts +++ b/src/main/shell-session.ts @@ -7,7 +7,7 @@ import { app } from "electron" import { Kubectl } from "./kubectl" import { Cluster } from "./cluster" import { ClusterPreferences } from "../common/cluster-store"; -import { helmCli } from "./helm-cli" +import { helmCli } from "./helm/helm-cli" import { isWindows } from "../common/vars"; import { tracker } from "../common/tracker"; diff --git a/src/renderer/_vue/store/modules/helm-repos.ts b/src/renderer/_vue/store/modules/helm-repos.ts index 195557ccaa..9f6ee669ca 100644 --- a/src/renderer/_vue/store/modules/helm-repos.ts +++ b/src/renderer/_vue/store/modules/helm-repos.ts @@ -1,6 +1,6 @@ import Vue from "vue" import { MutationTree, ActionTree, GetterTree } from "vuex" -import { HelmRepo, repoManager } from "../../../../main/helm-repo-manager" +import { HelmRepo, repoManager } from "../../../../main/helm/helm-repo-manager" export interface HelmRepoState { repos: HelmRepo[];