diff --git a/src/main/cluster.ts b/src/main/cluster.ts index b6323c0a0d..1398bf2f7f 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -95,7 +95,6 @@ export class Cluster implements ClusterInfo { this.contextName = kc.currentContext this.url = this.contextHandler.url this.apiUrl = kc.getCurrentCluster().server - await this.contextHandler.init() } public stopServer() { diff --git a/src/main/context-handler.ts b/src/main/context-handler.ts index 817b684d00..9f2977dfc4 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -1,4 +1,4 @@ -import { KubeConfig } from "@kubernetes/client-node" +import { KubeConfig, CoreV1Api } from "@kubernetes/client-node" import { readFileSync } from "fs" import * as http from "http" import { ServerOptions } from "http-proxy" @@ -7,6 +7,9 @@ import logger from "./logger" import { getFreePort } from "./port" import { KubeAuthProxy } from "./kube-auth-proxy" import { Cluster, ClusterPreferences } from "./cluster" +import { prometheusProviders } from "../common/prometheus-providers" +import { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry" +import { PrometheusLens } from "./prometheus/lens" export class ContextHandler { public contextName: string @@ -28,6 +31,7 @@ export class ContextHandler { protected defaultNamespace: string protected proxyPort: number protected kubernetesApi: string + protected prometheusProvider: string protected prometheusPath: string protected clusterName: string @@ -56,7 +60,6 @@ export class ContextHandler { this.defaultNamespace = kc.getContextObject(kc.currentContext).namespace this.url = `http://${this.id}.localhost:${cluster.port}/` this.kubernetesApi = `http://127.0.0.1:${cluster.port}/${this.id}` - this.setClusterPreferences(cluster.preferences) this.kc.clusters = [ { name: kc.getCurrentCluster().name, @@ -64,14 +67,17 @@ export class ContextHandler { skipTLSVerify: true } ] + this.setClusterPreferences(cluster.preferences) } public setClusterPreferences(clusterPreferences?: ClusterPreferences) { + this.prometheusProvider = clusterPreferences.prometheusProvider?.type + if (clusterPreferences && clusterPreferences.prometheus) { const prom = clusterPreferences.prometheus this.prometheusPath = `${prom.namespace}/services/${prom.service}:${prom.port}` } else { - this.prometheusPath = "lens-metrics/services/prometheus:80" + this.prometheusPath = null } if(clusterPreferences && clusterPreferences.clusterName) { this.clusterName = clusterPreferences.clusterName; @@ -80,28 +86,48 @@ export class ContextHandler { } } - public getPrometheusPath() { - return this.prometheusPath + protected async resolvePrometheusPath(): Promise { + const service = await this.getPrometheusService() + return `${service.namespace}/services/${service.service}:${service.port}` } - public async init() { - const currentCluster = this.kc.getCurrentCluster() - if (currentCluster.caFile) { - this.certData = readFileSync(currentCluster.caFile).toString() - } else if (currentCluster.caData) { - this.certData = Buffer.from(currentCluster.caData, "base64").toString("ascii") + public async getPrometheusProvider() { + if (!this.prometheusProvider) { + const service = await this.getPrometheusService() + logger.info(`using ${service.id} as prometheus provider`) + this.prometheusProvider = service.id } - const user = this.kc.getCurrentUser() - if (user.authProvider && user.authProvider.name === "oidc") { - const authConfig = user.authProvider.config - if (authConfig["idp-certificate-authority"]) { - this.authCertData = readFileSync(authConfig["idp-certificate-authority"]).toString() - } else if (authConfig["idp-certificate-authority-data"]) { - this.authCertData = Buffer.from(authConfig["idp-certificate-authority-data"], "base64").toString("ascii") + return prometheusProviders.find(p => p.id === this.prometheusProvider) + } + + public async getPrometheusService(): Promise { + const providers = this.prometheusProvider ? prometheusProviders.filter((p, _) => p.id == this.prometheusProvider) : prometheusProviders + const prometheusPromises: Promise[] = providers.map(async (provider: PrometheusProvider): Promise => { + const apiClient = this.kc.makeApiClient(CoreV1Api) + return await provider.getPrometheusService(apiClient) + }) + const resolvedPrometheusServices = await Promise.all(prometheusPromises) + const service = resolvedPrometheusServices.filter(n => n)[0] + if (service) { + return service + } else { + return { + id: "lens", + namespace: "lens-metrics", + service: "prometheus", + port: 80 } } } + public async getPrometheusPath(): Promise { + if (this.prometheusPath) return this.prometheusPath + + this.prometheusPath = await this.resolvePrometheusPath() + + return this.prometheusPath + } + public async getApiTarget(isWatchRequest = false) { if (this.apiTarget && !isWatchRequest) { return this.apiTarget diff --git a/src/main/prometheus/helm.ts b/src/main/prometheus/helm.ts index c69df755d5..f1462931df 100644 --- a/src/main/prometheus/helm.ts +++ b/src/main/prometheus/helm.ts @@ -1,10 +1,29 @@ import { PrometheusLens } from "./lens" +import { CoreV1Api } from "@kubernetes/client-node" +import { PrometheusService } from "./provider-registry"; +import logger from "../logger" export class PrometheusHelm extends PrometheusLens { - constructor() { - super() - this.id = "helm" - this.name = "Helm" - this.rateAccuracy = "5m" + id = "helm" + name = "Helm" + rateAccuracy = "5m" + + public async getPrometheusService(client: CoreV1Api): Promise { + const labelSelector = "app=prometheus,component=server,heritage=Helm" + try { + const serviceList = await client.listServiceForAllNamespaces(false, "", null, labelSelector) + const service = serviceList.body.items[0] + if (!service) return + + return { + id: this.id, + namespace: service.metadata.namespace, + service: service.metadata.name, + port: service.spec.ports[0].port + } + } catch(error) { + logger.warn(`PrometheusHelm: failed to list services: ${error.toString()}`) + return + } } -} \ No newline at end of file +} diff --git a/src/main/prometheus/lens.ts b/src/main/prometheus/lens.ts index f278000731..0b16f925e0 100644 --- a/src/main/prometheus/lens.ts +++ b/src/main/prometheus/lens.ts @@ -1,11 +1,28 @@ -import { PrometheusProvider, PrometheusQueryOpts, PrometheusClusterQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusPvcQuery, PrometheusIngressQuery } from "./provider-registry"; +import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry"; +import { CoreV1Api } from "@kubernetes/client-node"; +import logger from "../logger" export class PrometheusLens implements PrometheusProvider { id = "lens" name = "Lens" rateAccuracy = "1m" - public getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery { + public async getPrometheusService(client: CoreV1Api): Promise { + try { + const resp = await client.readNamespacedService("prometheus", "lens-metrics") + const service = resp.body + return { + id: this.id, + namespace: service.metadata.namespace, + service: service.metadata.name, + port: service.spec.ports[0].port + } + } catch(error) { + logger.warn(`PrometheusLens: failed to list services: ${error.toString()}`) + } + } + + public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { switch(opts.category) { case 'cluster': return { @@ -63,4 +80,4 @@ export class PrometheusLens implements PrometheusProvider { } } } -} \ No newline at end of file +} diff --git a/src/main/prometheus/operator.ts b/src/main/prometheus/operator.ts index a44abbe72f..1da01125a2 100644 --- a/src/main/prometheus/operator.ts +++ b/src/main/prometheus/operator.ts @@ -1,11 +1,36 @@ -import { PrometheusProvider, PrometheusQueryOpts, PrometheusClusterQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusPvcQuery, PrometheusIngressQuery } from "./provider-registry"; +import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry"; +import { CoreV1Api, V1Service } from "@kubernetes/client-node"; +import logger from "../logger"; export class PrometheusOperator implements PrometheusProvider { rateAccuracy = "1m" id = "operator" name = "Prometheus Operator" - public getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery { + public async getPrometheusService(client: CoreV1Api): Promise { + try { + let service: V1Service + for (const labelSelector of ["operated-prometheus=true", "self-monitor=true"]) { + if (!service) { + const serviceList = await client.listServiceForAllNamespaces(null, null, null, labelSelector) + service = serviceList.body.items[0] + } + } + if (!service) return + + return { + id: this.id, + namespace: service.metadata.namespace, + service: service.metadata.name, + port: service.spec.ports[0].port + } + } catch(error) { + logger.warn(`PrometheusOperator: failed to list services: ${error.toString()}`) + return + } + } + + public getQueries(opts: PrometheusQueryOpts): PrometheusQuery { switch(opts.category) { case 'cluster': return { @@ -63,4 +88,4 @@ export class PrometheusOperator implements PrometheusProvider { } } } -} \ No newline at end of file +} diff --git a/src/main/prometheus/provider-registry.ts b/src/main/prometheus/provider-registry.ts index d02feb1ab5..c59b2ad8ea 100644 --- a/src/main/prometheus/provider-registry.ts +++ b/src/main/prometheus/provider-registry.ts @@ -1,3 +1,5 @@ +import { CoreV1Api } from "@kubernetes/client-node" + export type PrometheusClusterQuery = { memoryUsage: string; memoryRequests: string; @@ -48,8 +50,20 @@ export type PrometheusQueryOpts = { [key: string]: string | any; }; +export type PrometheusQuery = PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery + +export type PrometheusService = { + id: string; + namespace: string; + service: string; + port: number; +} + export interface PrometheusProvider { - getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery; + id: string; + name: string; + getQueries(opts: PrometheusQueryOpts): PrometheusQuery; + getPrometheusService(client: CoreV1Api): Promise; } export type PrometheusProviderList = { @@ -73,4 +87,4 @@ export class PrometheusProviderRegistry { static getProviders(): PrometheusProvider[] { return Object.values(this.prometheusProviders) } -} \ No newline at end of file +} diff --git a/src/main/proxy.ts b/src/main/proxy.ts index ff998d79dd..538a960702 100644 --- a/src/main/proxy.ts +++ b/src/main/proxy.ts @@ -83,8 +83,6 @@ export class LensProxy { }, (250 * retryCount)) } } - - //return } res.writeHead(500, { 'Content-Type': 'text/plain' diff --git a/src/main/routes/metrics.ts b/src/main/routes/metrics.ts index b7b1a030b0..6bd965394d 100644 --- a/src/main/routes/metrics.ts +++ b/src/main/routes/metrics.ts @@ -13,7 +13,6 @@ class MetricsRoute extends LensApi { const { response, cluster} = request const query: MetricsQuery = request.payload; const serverUrl = `http://127.0.0.1:${cluster.port}/api-kube` - const metricsUrl = `${serverUrl}/api/v1/namespaces/${cluster.contextHandler.getPrometheusPath()}/proxy/api/v1/query_range` const headers = { "Host": `${cluster.id}.localhost:${cluster.port}`, "Content-type": "application/json", @@ -23,10 +22,12 @@ class MetricsRoute extends LensApi { queryParams[key] = value }) - const prometheusInstallationSource = cluster.preferences.prometheusProvider?.type || "lens" + let metricsUrl: string let prometheusProvider: PrometheusProvider try { - prometheusProvider = PrometheusProviderRegistry.getProvider(prometheusInstallationSource) + const prometheusPath = await cluster.contextHandler.getPrometheusPath() + metricsUrl = `${serverUrl}/api/v1/namespaces/${prometheusPath}/proxy/api/v1/query_range` + prometheusProvider = await cluster.contextHandler.getPrometheusProvider() } catch { this.respondJson(response, {}) return diff --git a/src/renderer/components/ClusterSettings/Preferences/index.vue b/src/renderer/components/ClusterSettings/Preferences/index.vue index 108b6ad207..a94f0b59d8 100644 --- a/src/renderer/components/ClusterSettings/Preferences/index.vue +++ b/src/renderer/components/ClusterSettings/Preferences/index.vue @@ -20,17 +20,7 @@
Prometheus

Use pre-installed Prometheus service for metrics. Please refer to the guide for possible configuration changes.

- - - + + + +
@@ -84,13 +86,25 @@ export default { }, prometheusPath: "", prometheusProvider: "", - prometheusProviders: [], + } + }, + computed: { + prometheusProviders: function() { + const providers = prometheusProviders.map((provider) => { + return { text: provider.name, value: provider.id } + }) + providers.unshift({text: "Auto detect", value: ""}) + + return providers; + }, + canEditPrometheusPath: function() { + if (this.prometheusProvider === "") return false + if (this.prometheusProvider === "lens") return false + + return true } }, mounted: async function() { - this.prometheusProviders = prometheusProviders.map((provider) => { - return { text: provider.name, value: provider.id } - }) this.updateValues() }, methods: { @@ -104,7 +118,7 @@ export default { if (this.cluster.preferences.prometheusProvider) { this.prometheusProvider = this.cluster.preferences.prometheusProvider.type } else { - this.prometheusProvider = "lens" + this.prometheusProvider = "" } }, parsePrometheusPath: function(path) {