/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import styles from "./add-cluster.module.scss"; import type { KubeConfig } from "@kubernetes/client-node"; import fse from "fs-extra"; import { debounce } from "lodash"; import { action, computed, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import React from "react"; import * as uuid from "uuid"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; import { docsUrl } from "../../../common/vars"; import { isDefined, iter } from "../../utils"; import { Button } from "../button"; import { Notifications } from "../notifications"; import { SettingLayout } from "../layout/setting-layout"; import { MonacoEditor } from "../monaco-editor"; import { withInjectables } from "@ogre-tools/injectable-react"; import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory.injectable"; import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import type { EmitAppEvent } from "../../../common/app-event-bus/emit-event.injectable"; import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import type { GetDirnameOfPath } from "../../../common/path/get-dirname.injectable"; import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable"; interface Option { config: KubeConfig; error?: string; } interface Dependencies { getCustomKubeConfigDirectory: (directoryName: string) => string; navigateToCatalog: NavigateToCatalog; getDirnameOfPath: GetDirnameOfPath; emitAppEvent: EmitAppEvent; } function getContexts(config: KubeConfig): Map { return new Map( splitConfig(config) .map(({ config, validationResult }) => [config.currentContext, { config, error: validationResult.error?.toString(), }]), ); } @observer class NonInjectedAddCluster extends React.Component { @observable kubeContexts = observable.map(); @observable customConfig = ""; @observable isWaiting = false; @observable errors: string[] = []; constructor(props: Dependencies) { super(props); makeObservable(this); } componentDidMount() { this.props.emitAppEvent({ name: "cluster-add", action: "start" }); } @computed get allErrors(): string[] { return [ ...this.errors, ...iter.map(this.kubeContexts.values(), ({ error }) => error), ].filter(isDefined); } readonly refreshContexts = debounce(action(() => { const { config, error } = loadConfigFromString(this.customConfig.trim() || "{}"); this.kubeContexts.replace(getContexts(config)); if (error) { this.errors.push(error.toString()); } if (config.contexts.length === 0) { this.errors.push('No contexts defined, either missing the "contexts" field, or it is empty.'); } }), 500); addClusters = action(async () => { this.isWaiting = true; this.props.emitAppEvent({ name: "cluster-add", action: "click" }); try { const absPath = this.props.getCustomKubeConfigDirectory(uuid.v4()); await fse.ensureDir(this.props.getDirnameOfPath(absPath)); await fse.writeFile(absPath, this.customConfig.trim(), { encoding: "utf-8", mode: 0o600 }); Notifications.ok(`Successfully added ${this.kubeContexts.size} new cluster(s)`); return this.props.navigateToCatalog(); } catch (error) { Notifications.error(`Failed to add clusters: ${error}`); } }); render() { return (

Add Clusters from Kubeconfig

{"Clusters added here are "} not {" merged into the "} ~/.kube/config {" file. "} Read more about adding clusters.

{ this.customConfig = value; this.errors.length = 0; this.refreshContexts(); }} />
{this.allErrors.length > 0 && ( <>

KubeConfig Yaml Validation Errors:

{this.allErrors.map(error =>
{error}
)} )}
); } } export const AddCluster = withInjectables(NonInjectedAddCluster, { getProps: (di) => ({ getCustomKubeConfigDirectory: di.inject(getCustomKubeConfigDirectoryInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable), getDirnameOfPath: di.inject(getDirnameOfPathInjectable), emitAppEvent: di.inject(emitAppEventInjectable), }), });