diff --git a/src/features/metrics.ts b/src/features/metrics.ts index e1a9690246..98e71e50d2 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.contextHandler.kc.makeApiClient(k8s.StorageV1Api) + const storageClient = cluster.proxyKubeconfig().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.contextHandler.kc.makeApiClient(RbacAuthorizationV1Api) + const rbacClient = cluster.proxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) try { - await this.deleteNamespace(cluster.contextHandler.kc, "lens-metrics") + await this.deleteNamespace(cluster.proxyKubeconfig(), "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 eea957a6b4..d1f1dcf381 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.contextHandler.kc.makeApiClient(RbacAuthorizationV1Api) + const rbacClient = cluster.proxyKubeconfig().makeApiClient(RbacAuthorizationV1Api) try { await rbacClient.deleteClusterRole("lens-user"); await rbacClient.deleteClusterRoleBinding("lens-user"); diff --git a/src/main/cluster.ts b/src/main/cluster.ts index ffcdb1816b..e9c1ab5c1a 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -92,13 +92,19 @@ export class Cluster implements ClusterInfo { return this.kubeconfigManager.getPath() } + public proxyKubeconfig() { + const kc = new KubeConfig() + kc.loadFromFile(this.kubeconfigManager.getPath()) + return kc + } + public async init(kc: KubeConfig) { + this.apiUrl = kc.getCurrentCluster().server 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 - this.apiUrl = kc.getCurrentCluster().server } public stopServer() { @@ -131,7 +137,7 @@ export class Cluster implements ClusterInfo { if (this.accessible) { this.distribution = this.detectKubernetesDistribution(this.version) - this.features = await fm.getFeatures(this.contextHandler) + this.features = await fm.getFeatures(this) this.isAdmin = await this.isClusterAdmin() this.nodes = await this.getNodeCount() this.kubeCtl = new Kubectl(this.version) @@ -156,7 +162,7 @@ export class Cluster implements ClusterInfo { id: this.id, workspace: this.workspace, url: this.url, - contextName: this.contextHandler.kc.currentContext, + contextName: this.contextName, apiUrl: this.apiUrl, online: this.online, accessible: this.accessible, @@ -216,7 +222,7 @@ export class Cluster implements ClusterInfo { } public async canI(resourceAttributes: V1ResourceAttributes): Promise { - const authApi = this.contextHandler.kc.makeApiClient(AuthorizationV1Api) + const authApi = this.proxyKubeconfig().makeApiClient(AuthorizationV1Api) try { const accessReview = await authApi.createSelfSubjectAccessReview({ apiVersion: "authorization.k8s.io/v1", @@ -278,7 +284,7 @@ export class Cluster implements ClusterInfo { if (!this.isAdmin) { return 0; } - const client = this.contextHandler.kc.makeApiClient(CoreV1Api); + const client = this.proxyKubeconfig().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 f8d104ae53..9f527a9693 100644 --- a/src/main/context-handler.ts +++ b/src/main/context-handler.ts @@ -15,7 +15,7 @@ export class ContextHandler { public contextName: string public id: string public url: string - public kc: KubeConfig + public certData: string public authCertData: string public cluster: Cluster @@ -37,13 +37,11 @@ export class ContextHandler { constructor(kc: KubeConfig, cluster: Cluster) { this.id = cluster.id - this.kc = kc - this.kc.setCurrentContext(cluster.contextName) this.cluster = cluster - this.clusterUrl = url.parse(kc.getCurrentCluster().server) + this.clusterUrl = url.parse(cluster.apiUrl) //url.parse(kc.getCurrentCluster().server) this.contextName = cluster.contextName; - this.defaultNamespace = kc.getContextObject(kc.currentContext).namespace + this.defaultNamespace = kc.getContextObject(cluster.contextName).namespace this.url = `http://${this.id}.localhost:${cluster.port}/` this.kubernetesApi = `http://127.0.0.1:${cluster.port}/${this.id}` @@ -89,7 +87,7 @@ export class ContextHandler { 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) + const apiClient = this.cluster.proxyKubeconfig().makeApiClient(CoreV1Api) return await provider.getPrometheusService(apiClient) }) const resolvedPrometheusServices = await Promise.all(prometheusPromises) diff --git a/src/main/feature-manager.ts b/src/main/feature-manager.ts index 4f6a7fb72b..6796f5d662 100644 --- a/src/main/feature-manager.ts +++ b/src/main/feature-manager.ts @@ -1,4 +1,4 @@ -import { ContextHandler } from "./context-handler" +import { KubeConfig } from "@kubernetes/client-node" import logger from "./logger"; import { Cluster } from "./cluster"; import { Feature, FeatureStatusMap } from "./feature" @@ -10,17 +10,19 @@ const ALL_FEATURES: any = { 'user-mode': new UserModeFeature(null), } -export async function getFeatures(clusterContext: ContextHandler): Promise { +export async function getFeatures(cluster: Cluster): Promise { return new Promise(async (resolve, reject) => { const result: FeatureStatusMap = {}; - logger.debug(`features for ${clusterContext.contextName}`); + logger.debug(`features for ${cluster.contextName}`); for (const key in ALL_FEATURES) { logger.debug(`feature ${key}`); if (ALL_FEATURES.hasOwnProperty(key)) { logger.debug("getting feature status..."); const feature = ALL_FEATURES[key] as Feature; - - const status = await feature.featureStatus(clusterContext.kc); + const kc = new KubeConfig() + kc.loadFromFile(cluster.proxyKubeconfigPath()) + + const status = await feature.featureStatus(kc); result[feature.name] = status } else { diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index 02d4e0bb7b..743bac88f5 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -35,7 +35,7 @@ export class KubeAuthProxy { "--kubeconfig", this.cluster.kubeConfigPath, "--context", this.cluster.contextName, "--accept-hosts", ".*", - "--reject-paths", "^[^/]", + "--reject-paths", "^[^/]" ] if (process.env.DEBUG_PROXY === "true") { args = args.concat(["-v", "9"]) diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index 17849c26e0..7123b0158a 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -27,6 +27,8 @@ export class KubeconfigManager { protected createTemporaryKubeconfig(): string { ensureDir(this.configDir) const path = `${this.configDir}/${randomFileName("kubeconfig")}` + const originalKc = new KubeConfig() + originalKc.loadFromFile(this.cluster.kubeConfigPath) const kc = { clusters: [ { @@ -43,7 +45,7 @@ export class KubeconfigManager { { name: this.cluster.contextName, cluster: this.cluster.contextName, - namespace: this.cluster.contextHandler.kc.getContextObject(this.cluster.contextName).namespace, + namespace: originalKc.getContextObject(this.cluster.contextName).namespace, user: "proxy" } ], diff --git a/src/main/node-shell-session.ts b/src/main/node-shell-session.ts index 4674307fc0..6369b6bc8b 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, pathToKubeconfig, cluster) this.nodeName = nodeName this.podId = `node-shell-${uuid()}` - this.kc = cluster.contextHandler.kc + this.kc = cluster.proxyKubeconfig() } public async open() { diff --git a/src/main/routes/config.ts b/src/main/routes/config.ts index c7e4164dbe..d726a928f8 100644 --- a/src/main/routes/config.ts +++ b/src/main/routes/config.ts @@ -45,7 +45,7 @@ const apiResources = [ ] async function getAllowedNamespaces(cluster: Cluster) { - const api = cluster.contextHandler.kc.makeApiClient(CoreV1Api) + const api = cluster.proxyKubeconfig().makeApiClient(CoreV1Api) try { const namespaceList = await api.listNamespace() const nsAccessStatuses = await Promise.all( @@ -58,9 +58,8 @@ async function getAllowedNamespaces(cluster: Cluster) { return namespaceList.body.items .filter((ns, i) => nsAccessStatuses[i]) .map(ns => ns.metadata.name) - } catch (error) { - const kc = cluster.contextHandler.kc - const ctx = kc.getContextObject(kc.currentContext) + } catch(error) { + const ctx = cluster.proxyKubeconfig().getContextObject(cluster.contextName) if (ctx.namespace) { return [ctx.namespace] } diff --git a/src/main/routes/kubeconfig.ts b/src/main/routes/kubeconfig.ts index 6a6f173f3a..2d77c4faa2 100644 --- a/src/main/routes/kubeconfig.ts +++ b/src/main/routes/kubeconfig.ts @@ -12,7 +12,7 @@ function generateKubeConfig(username: string, secret: V1Secret, cluster: Cluster { 'name': cluster.contextName, 'cluster': { - 'server': cluster.contextHandler.kc.getCurrentCluster().server, + 'server': cluster.apiUrl, 'certificate-authority-data': secret.data["ca.crt"] } } @@ -44,7 +44,7 @@ class KubeconfigRoute extends LensApi { public async routeServiceAccountRoute(request: LensApiRequest) { const { params, response, cluster} = request - const client = cluster.contextHandler.kc.makeApiClient(CoreV1Api); + const client = cluster.proxyKubeconfig().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/watch.ts b/src/main/routes/watch.ts index 6280b3e4e4..4df98f5082 100644 --- a/src/main/routes/watch.ts +++ b/src/main/routes/watch.ts @@ -38,7 +38,17 @@ class ApiWatcher { clearInterval(this.processor) } logger.debug("Stopping watcher for api: " + this.apiUrl) - this.watchRequest.abort() + try { + this.watchRequest.abort() + this.sendEvent({ + type: "STREAM_END", + url: this.apiUrl, + status: 410, + }) + logger.debug("watch aborted") + } catch (error) { + logger.error("Watch abort errored:" + error) + } } private watchHandler(phase: string, obj: any) { @@ -49,6 +59,7 @@ class ApiWatcher { } private doneHandler(error: Error) { + logger.debug("********** watch.doneHandler") if (error) logger.warn("watch ended: " + error.toString()) this.sendEvent({ @@ -82,9 +93,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)) apis.forEach(apiUrl => { - const watcher = new ApiWatcher(apiUrl, cluster.contextHandler.kc, response) + const watcher = new ApiWatcher(apiUrl, cluster.proxyKubeconfig() /*cluster.contextHandler.kc*/, response) watcher.start() watchers.push(watcher) }) 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 a0fcc81de5..0ef3631da3 100644 --- a/src/migrations/cluster-store/3.6.0-beta.1.ts +++ b/src/migrations/cluster-store/3.6.0-beta.1.ts @@ -17,6 +17,7 @@ export function migration(store: any) { console.log("num clusters to migrate: ", storedClusters.length) for (const cluster of storedClusters ) { // TODO Should probably guard this, not to make the whole migration fail if one cluster fails!? + // take the embedded kubeconfig and dump it into a file const kubeConfigFile = writeEmbeddedKubeConfig(cluster.id, cluster.kubeConfig) cluster.kubeConfigPath = kubeConfigFile