/** * 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 path from "path"; import React from "react"; import * as uuid from "uuid"; import { catalogURL } from "../../../common/routes"; import { appEventBus } from "../../../common/app-event-bus/event-bus"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; import { docsUrl } from "../../../common/vars"; import { navigate } from "../../navigation"; import { 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/get-custom-kube-config-directory.injectable"; interface Option { config: KubeConfig; error?: string; } interface Dependencies { getCustomKubeConfigDirectory: (directoryName: string) => string } function getContexts(config: KubeConfig): Map { return new Map( splitConfig(config) .map(({ config, error }) => [config.currentContext, { config, error, }]), ); } @observer class NonInjectedAddCluster extends React.Component { @observable kubeContexts = observable.map(); @observable customConfig = ""; @observable isWaiting = false; @observable errors: string[] = []; constructor(dependencies: Dependencies) { super(dependencies); makeObservable(this); } componentDidMount() { appEventBus.emit({ name: "cluster-add", action: "start" }); } @computed get allErrors(): string[] { return [ ...this.errors, ...iter.map(this.kubeContexts.values(), ({ error }) => error), ].filter(Boolean); } 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; appEventBus.emit({ name: "cluster-add", action: "click" }); try { const absPath = this.props.getCustomKubeConfigDirectory(uuid.v4()); await fse.ensureDir(path.dirname(absPath)); await fse.writeFile(absPath, this.customConfig.trim(), { encoding: "utf-8", mode: 0o600 }); Notifications.ok(`Successfully added ${this.kubeContexts.size} new cluster(s)`); return navigate(catalogURL()); } 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, ), }), });