Add the ability to sync kube config files (#2567)

* Add the ability to sync kube config files

- Will update when the files changes

- add KUBECONFIG_SYNC label

- fix rebase and change to addObservableSource

- move UI to user settings

- support shallow folder watching

- add some unit tests for the diff-er

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* responding to review comments

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix tests and add try/catch

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* always sync c&p folder, remove bad rebase

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix preferences

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Fix settings saving and catalog view

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix unit tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix synced clusters not connectable

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* change to non-complete shallow watching

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* fix sizing

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Catch readStream errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* don't clear UserStore on non-existant preference field, fix unlinking not removing items from source

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* change label to file

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2021-04-30 16:48:20 +03:00
1 parent f06d330835
commit 998f7aa934
32 files changed
+1215 -236

No files matched your search

+71 -41
View File
@@ -6,7 +6,7 @@ import yaml from "js-yaml";
import logger from "../main/logger";
import commandExists from "command-exists";
import { ExecValidationNotFoundError } from "./custom-errors";
import { newClusters, newContexts, newUsers } from "@kubernetes/client-node/dist/config_types";
import { Cluster, Context, newClusters, newContexts, newUsers, User } from "@kubernetes/client-node/dist/config_types";
export type KubeConfigValidationOpts = {
validateCluster?: boolean;
@@ -28,11 +28,26 @@ function readResolvedPathSync(filePath: string): string {
return fse.readFileSync(path.resolve(resolveTilde(filePath)), "utf8");
}
function checkRawContext(rawContext: any): boolean {
return rawContext.name && rawContext.context?.cluster && rawContext.context?.user;
function checkRawCluster(rawCluster: any): boolean {
return Boolean(rawCluster?.name && rawCluster?.cluster?.server);
}
function loadToOptions(rawYaml: string): any {
function checkRawUser(rawUser: any): boolean {
return Boolean(rawUser?.name);
}
function checkRawContext(rawContext: any): boolean {
return Boolean(rawContext.name && rawContext.context?.cluster && rawContext.context?.user);
}
export interface KubeConfigOptions {
clusters: Cluster[];
users: User[];
contexts: Context[];
currentContext: string;
}
function loadToOptions(rawYaml: string): KubeConfigOptions {
const obj = yaml.safeLoad(rawYaml);
if (typeof obj !== "object" || !obj) {
@@ -40,16 +55,14 @@ function loadToOptions(rawYaml: string): any {
}
const { clusters: rawClusters, users: rawUsers, contexts: rawContexts, "current-context": currentContext } = obj;
const clusters = newClusters(rawClusters);
const users = newUsers(rawUsers);
const clusters = newClusters(rawClusters?.filter(checkRawCluster));
const users = newUsers(rawUsers?.filter(checkRawUser));
const contexts = newContexts(rawContexts?.filter(checkRawContext));
return { clusters, users, contexts, currentContext };
}
export function loadConfig(pathOrContent?: string): KubeConfig {
const content = fse.pathExistsSync(pathOrContent) ? readResolvedPathSync(pathOrContent) : pathOrContent;
const options = loadToOptions(content);
export function loadFromOptions(options: KubeConfigOptions): KubeConfig {
const kc = new KubeConfig();
// need to load using the kubernetes client to generate a kubeconfig object
@@ -58,6 +71,18 @@ export function loadConfig(pathOrContent?: string): KubeConfig {
return kc;
}
export function loadConfig(pathOrContent?: string): KubeConfig {
return loadConfigFromString(
fse.pathExistsSync(pathOrContent)
? readResolvedPathSync(pathOrContent)
: pathOrContent
);
}
export function loadConfigFromString(content: string): KubeConfig {
return loadFromOptions(loadToOptions(content));
}
/**
* KubeConfig is valid when there's at least one of each defined:
* - User
@@ -181,42 +206,47 @@ export function getNodeWarningConditions(node: V1Node) {
/**
* Checks if `config` has valid `Context`, `User`, `Cluster`, and `exec` fields (if present when required)
*
* Note: This function returns an error instead of throwing it, returning `undefined` if the validation passes
*/
export function validateKubeConfig (config: KubeConfig, contextName: string, validationOpts: KubeConfigValidationOpts = {}) {
// 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
export function validateKubeConfig(config: KubeConfig, contextName: string, validationOpts: KubeConfigValidationOpts = {}): Error | undefined {
try {
// 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
const { validateUser = true, validateCluster = true, validateExec = true } = validationOpts;
const { validateUser = true, validateCluster = true, validateExec = true } = validationOpts;
const contextObject = config.getContextObject(contextName);
const contextObject = config.getContextObject(contextName);
// Validate the Context Object
if (!contextObject) {
throw new Error(`No valid context object provided in kubeconfig for context '${contextName}'`);
}
// Validate the Cluster Object
if (validateCluster && !config.getCluster(contextObject.cluster)) {
throw new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`);
}
const user = config.getUser(contextObject.user);
// Validate the User Object
if (validateUser && !user) {
throw new Error(`No valid user object provided in kubeconfig for context '${contextName}'`);
}
// Validate exec command if present
if (validateExec && 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
if (!commandExists.sync(execCommand)) {
logger.debug(`validateKubeConfig: exec command ${String(execCommand)} in kubeconfig ${contextName} not found`);
throw new ExecValidationNotFoundError(execCommand, isAbsolute);
// Validate the Context Object
if (!contextObject) {
return new Error(`No valid context object provided in kubeconfig for context '${contextName}'`);
}
// Validate the Cluster Object
if (validateCluster && !config.getCluster(contextObject.cluster)) {
return new Error(`No valid cluster object provided in kubeconfig for context '${contextName}'`);
}
const user = config.getUser(contextObject.user);
// Validate the User Object
if (validateUser && !user) {
return new Error(`No valid user object provided in kubeconfig for context '${contextName}'`);
}
// Validate exec command if present
if (validateExec && 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
if (!commandExists.sync(execCommand)) {
return new ExecValidationNotFoundError(execCommand, isAbsolute);
}
}
} catch (error) {
return error;
}
}