import "./add-cluster.scss"; import os from "os"; import React from "react"; import { observer } from "mobx-react"; import { action, observable, runInAction } from "mobx"; import { remote } from "electron"; import { KubeConfig } from "@kubernetes/client-node"; import { Select, SelectOption } from "../select"; import { DropFileInput, Input } from "../input"; import { AceEditor } from "../ace-editor"; import { Button } from "../button"; import { Icon } from "../icon"; import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig, validateKubeConfig } from "../../../common/kube-helpers"; import { ClusterModel, ClusterStore, clusterStore } from "../../../common/cluster-store"; import { workspaceStore } from "../../../common/workspace-store"; import { v4 as uuid } from "uuid"; import { navigate } from "../../navigation"; import { userStore } from "../../../common/user-store"; import { clusterViewURL } from "../cluster-manager/cluster-view.route"; import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; import { Tab, Tabs } from "../tabs"; import { ExecValidationNotFoundError } from "../../../common/custom-errors"; import { appEventBus } from "../../../common/event-bus"; import { PageLayout } from "../layout/page-layout"; import { docsUrl } from "../../../common/vars"; enum KubeConfigSourceTab { FILE = "file", TEXT = "text" } @observer export class AddCluster extends React.Component { @observable.ref kubeConfigLocal: KubeConfig; @observable.ref error: React.ReactNode; @observable kubeContexts = observable.map(); // available contexts from kubeconfig-file or user-input @observable selectedContexts = observable.array(); @observable sourceTab = KubeConfigSourceTab.FILE; @observable kubeConfigPath = ""; @observable customConfig = ""; @observable proxyServer = ""; @observable isWaiting = false; @observable showSettings = false; componentDidMount() { clusterStore.setActive(null); this.setKubeConfig(userStore.kubeConfigPath); appEventBus.emit({ name: "cluster-add", action: "start" }); } componentWillUnmount() { userStore.markNewContextsAsSeen(); } @action setKubeConfig(filePath: string, { throwError = false } = {}) { try { this.kubeConfigLocal = loadConfig(filePath); validateConfig(this.kubeConfigLocal); this.refreshContexts(); this.kubeConfigPath = filePath; userStore.kubeConfigPath = filePath; // save to store } catch (err) { Notifications.error(
Can't setup {filePath} as kubeconfig: {String(err)}
); if (throwError) { throw err; } } } @action refreshContexts() { this.selectedContexts.clear(); this.kubeContexts.clear(); switch (this.sourceTab) { case KubeConfigSourceTab.FILE: const contexts = this.getContexts(this.kubeConfigLocal); this.kubeContexts.replace(contexts); break; case KubeConfigSourceTab.TEXT: try { this.error = ""; const contexts = this.getContexts(loadConfig(this.customConfig || "{}")); this.kubeContexts.replace(contexts); } catch (err) { this.error = String(err); } break; } if (this.kubeContexts.size === 1) { this.selectedContexts.push(this.kubeContexts.keys().next().value); } } getContexts(config: KubeConfig): Map { const contexts = new Map(); splitConfig(config).forEach(config => { contexts.set(config.currentContext, config); }); return contexts; } selectKubeConfigDialog = async () => { const { dialog, BrowserWindow } = remote; const { canceled, filePaths } = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), { defaultPath: this.kubeConfigPath, properties: ["openFile", "showHiddenFiles"], message: `Select custom kubeconfig file`, buttonLabel: `Use configuration`, }); if (!canceled && filePaths.length) { this.setKubeConfig(filePaths[0]); } }; onDropKubeConfig = (files: File[]) => { this.sourceTab = KubeConfigSourceTab.FILE; this.setKubeConfig(files[0].path); }; @action addClusters = () => { let newClusters: ClusterModel[] = []; try { if (!this.selectedContexts.length) { this.error = "Please select at least one cluster context"; return; } this.error = ""; this.isWaiting = true; appEventBus.emit({ name: "cluster-add", action: "click" }); newClusters = this.selectedContexts.filter(context => { try { const kubeConfig = this.kubeContexts.get(context); validateKubeConfig(kubeConfig); return true; } catch (err) { this.error = String(err.message); if (err instanceof ExecValidationNotFoundError) { Notifications.error(<>Error while adding cluster(s): {this.error}); return false; } else { throw new Error(err); } } }).map(context => { const clusterId = uuid(); const kubeConfig = this.kubeContexts.get(context); const kubeConfigPath = this.sourceTab === KubeConfigSourceTab.FILE ? this.kubeConfigPath // save link to original kubeconfig in file-system : ClusterStore.embedCustomKubeConfig(clusterId, kubeConfig); // save in app-files folder return { id: clusterId, kubeConfigPath, workspace: workspaceStore.currentWorkspaceId, contextName: kubeConfig.currentContext, preferences: { clusterName: kubeConfig.currentContext, httpsProxy: this.proxyServer || undefined, }, }; }); runInAction(() => { clusterStore.addClusters(...newClusters); if (newClusters.length === 1) { const clusterId = newClusters[0].id; clusterStore.setActive(clusterId); navigate(clusterViewURL({ params: { clusterId } })); } else { if (newClusters.length > 1) { Notifications.ok( <>Successfully imported {newClusters.length} cluster(s) ); } } }); this.refreshContexts(); } catch (err) { this.error = String(err); Notifications.error(<>Error while adding cluster(s): {this.error}); } finally { this.isWaiting = false; } }; renderInfo() { return (

Add clusters by clicking the Add Cluster button. You'll need to obtain a working kubeconfig for the cluster you want to add. You can either browse it from the file system or paste it as a text from the clipboard. Read more about adding clusters here.

); } renderKubeConfigSource() { return ( <> {this.sourceTab === KubeConfigSourceTab.FILE && (
this.kubeConfigPath = v} onBlur={this.onKubeConfigInputBlur} /> {this.kubeConfigPath !== kubeConfigDefaultPath && ( this.setKubeConfig(kubeConfigDefaultPath)} tooltip="Reset" /> )}
Pro-Tip: you can also drag-n-drop kubeconfig file to this area
)} {this.sourceTab === KubeConfigSourceTab.TEXT && (
{ this.customConfig = value; this.refreshContexts(); }} /> Pro-Tip: paste kubeconfig to get available contexts
)} ); } renderContextSelector() { const allContexts = Array.from(this.kubeContexts.keys()); const placeholder = this.selectedContexts.length > 0 ? <>Selected contexts: {this.selectedContexts.length} : "Select contexts"; return (
this.proxyServer = value} theme="round-black" /> {"A HTTP proxy server URL (format: http://
:)."}
)} {this.error && (
{this.error}
)}
); } }