diff --git a/package.json b/package.json
index ea65b9d2f0..43c5c31359 100644
--- a/package.json
+++ b/package.json
@@ -175,6 +175,7 @@
"@types/tar": "^4.0.3",
"array-move": "^3.0.0",
"chalk": "^4.1.0",
+ "command-exists": "1.2.9",
"conf": "^7.0.1",
"crypto-js": "^4.0.0",
"electron-updater": "^4.3.1",
diff --git a/src/common/custom-errors.ts b/src/common/custom-errors.ts
new file mode 100644
index 0000000000..3c7750487a
--- /dev/null
+++ b/src/common/custom-errors.ts
@@ -0,0 +1,12 @@
+export class ExecValidationNotFoundError extends Error {
+ constructor(execPath: string, isAbsolute: boolean) {
+ super(`User Exec command "${execPath}" not found on host.`);
+ let message = `User Exec command "${execPath}" not found on host.`;
+ if (!isAbsolute) {
+ message += ` Please ensure binary is found in PATH or use absolute path to binary in Kubeconfig`;
+ }
+ this.message = message;
+ this.name = this.constructor.name;
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
\ No newline at end of file
diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts
index 18f472243b..8f517ba555 100644
--- a/src/common/kube-helpers.ts
+++ b/src/common/kube-helpers.ts
@@ -4,6 +4,8 @@ import path from "path"
import os from "os"
import yaml from "js-yaml"
import logger from "../main/logger";
+import commandExists from "command-exists";
+import { ExecValidationNotFoundError } from "./custom-errors";
export const kubeConfigDefaultPath = path.join(os.homedir(), '.kube', 'config');
@@ -140,3 +142,28 @@ export function getNodeWarningConditions(node: V1Node) {
c.status.toLowerCase() === "true" && c.type !== "Ready" && c.type !== "HostUpgrades"
)
}
+
+/**
+ * Validates kubeconfig supplied in the add clusters screen. At present this will just validate
+ * the User struct, specifically the command passed to the exec substructure.
+ */
+export function validateKubeConfig (config: KubeConfig) {
+ // we only receive a single context, cluster & user object here so lets validate them as this
+ // will be called when we add a new cluster to Lens
+ logger.debug(`validateKubeConfig: validating kubeconfig - ${JSON.stringify(config)}`);
+
+ // Validate the User Object
+ const user = config.getCurrentUser();
+ if (user.exec) {
+ const execCommand = user.exec["command"];
+ // check if the command is absolute or not
+ const isAbsolute = path.isAbsolute(execCommand);
+ // validate the exec struct in the user object, start with the command field
+ logger.debug(`validateKubeConfig: validating user exec command - ${JSON.stringify(execCommand)}`);
+
+ if (!commandExists.sync(execCommand)) {
+ logger.debug(`validateKubeConfig: exec command ${String(execCommand)} in kubeconfig ${config.currentContext} not found`);
+ throw new ExecValidationNotFoundError(execCommand, isAbsolute);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx
index a0414541d8..60d609d4f0 100644
--- a/src/renderer/components/+add-cluster/add-cluster.tsx
+++ b/src/renderer/components/+add-cluster/add-cluster.tsx
@@ -13,7 +13,7 @@ import { AceEditor } from "../ace-editor";
import { Button } from "../button";
import { Icon } from "../icon";
import { WizardLayout } from "../layout/wizard-layout";
-import { kubeConfigDefaultPath, loadConfig, splitConfig, validateConfig } from "../../../common/kube-helpers";
+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"
@@ -23,6 +23,7 @@ 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";
enum KubeConfigSourceTab {
FILE = "file",
@@ -118,14 +119,32 @@ export class AddCluster extends React.Component {
}
addClusters = () => {
+ const configValidationErrors:string[] = [];
+ let newClusters: ClusterModel[] = [];
+
try {
if (!this.selectedContexts.length) {
this.error = Please select at least one cluster context
return;
}
this.error = ""
- this.isWaiting = true
- const newClusters: ClusterModel[] = this.selectedContexts.map(context => {
+ this.isWaiting = true
+
+ 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
@@ -141,7 +160,8 @@ export class AddCluster extends React.Component {
httpsProxy: this.proxyServer || undefined,
},
}
- });
+ })
+
runInAction(() => {
clusterStore.addCluster(...newClusters);
if (newClusters.length === 1) {
@@ -149,9 +169,11 @@ export class AddCluster extends React.Component {
clusterStore.setActive(clusterId);
navigate(clusterViewURL({ params: { clusterId } }));
} else {
- Notifications.ok(
- Successfully imported {newClusters.length} cluster(s)
- );
+ if (newClusters.length > 1) {
+ Notifications.ok(
+ Successfully imported {newClusters.length} cluster(s)
+ );
+ }
}
})
this.refreshContexts();
diff --git a/types/command-exists.d.ts b/types/command-exists.d.ts
new file mode 100644
index 0000000000..b5375ae390
--- /dev/null
+++ b/types/command-exists.d.ts
@@ -0,0 +1,16 @@
+// Type definitions for command-exists 1.2
+// Project: https://github.com/mathisonian/command-exists
+// Definitions by: BendingBender
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+export = commandExists;
+
+declare function commandExists(commandName: string): Promise;
+declare function commandExists(
+ commandName: string,
+ cb: (error: null, exists: boolean) => void
+): void;
+
+declare namespace commandExists {
+ function sync(commandName: string): boolean;
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 1c92dd0978..a8dcc87642 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3888,6 +3888,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+command-exists@1.2.9:
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
+ integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
+
commander@*, commander@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"