Skip to content

Commit

Permalink
'proxy process watcher wip'
Browse files Browse the repository at this point in the history
  • Loading branch information
juozasg authored and Kingdon Barrett committed Jul 20, 2023
1 parent 22f60c0 commit 8ad201c
Show file tree
Hide file tree
Showing 17 changed files with 416 additions and 307 deletions.
98 changes: 98 additions & 0 deletions src/cli/kubernetes/kubectlProxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as k8s from '@kubernetes/client-node';
import { kubeConfig, onCurrentContextChanged } from 'cli/kubernetes/kubernetesConfig';
import { invokeKubectlCommand } from './kubernetesToolsKubectl';
import { shell } from 'cli/shell/exec';
import { ChildProcess } from 'child_process';


export let kubeProxyConfig: k8s.KubeConfig | undefined;
let kubectlProxyProcess: ChildProcess | undefined;

// export const fluxInformers: Record<string, FluxInformer> = {};
export function initKubeProxy() {
restartKubeProxy();

// keep alive
setInterval(() => {
if(!kubeProxyConfig) {
restartKubeProxy();
}
}, 1000);

// user switched kubeconfig context
onCurrentContextChanged.event(() => {
restartKubeProxy();
});
}

function procStarted(p: ChildProcess) {
kubectlProxyProcess = p;
console.log('got a proc!', p);

p.on('exit', code => {
console.log('proc exit', p, code);
restartKubeProxy();
});

p.on('error', err => {
console.log('proc error', p, err);
restartKubeProxy();
});

p.stdout?.on('data', (data: string) => {
console.log(`proxy STDOUT: ${data}`);
if(data.includes('Starting to serve on')) {
const port = parseInt(data.split(':')[1].trim());
kubeProxyConfig = makeProxyConfig(port);
}
});

p.stderr?.on('data', (data: string) => {
console.log(`proxy STDERR: ${data}`);
restartKubeProxy();
});
}


async function restartKubeProxy() {
console.log('restartKubeProxy()');
await stopKubeProxy();

shell.exec('kubectl proxy -p 0', {callback: procStarted});
}

async function stopKubeProxy() {
// kubeProxyPort = undefined;
kubeProxyConfig = undefined;
if(kubectlProxyProcess) {
kubectlProxyProcess.kill();
kubectlProxyProcess = undefined;
}
}



function makeProxyConfig(port: number) {
const cluster = {
name: kubeConfig.getCurrentCluster()?.name,
server: `http://127.0.0.1:${port}`,
};

const user = kubeConfig.getCurrentUser();

const context = {
name: kubeConfig.getCurrentContext(),
user: user?.name,
cluster: cluster.name,
};

const kc = new k8s.KubeConfig();
kc.loadFromOptions({
clusters: [cluster],
users: [user],
contexts: [context],
currentContext: context.name,
});
return kc;
}

205 changes: 131 additions & 74 deletions src/cli/kubernetes/kubernetesConfig.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,87 @@
import deepEqual from 'lite-deep-equal';
import safesh from 'shell-escape-tag';
import { window } from 'vscode';
import { EventEmitter, window } from 'vscode';

import * as k8s from '@kubernetes/client-node';
import { shellCodeError } from 'cli/shell/exec';
import { setVSCodeContext, telemetry } from 'extension';
import { Errorable, aresult, failed, succeeded } from 'types/errorable';
import { Errorable, aresult, failed, result, succeeded } from 'types/errorable';
import { ContextId } from 'types/extensionIds';
import { KubernetesConfig, KubernetesContextWithCluster } from 'types/kubernetes/kubernetesConfig';
import { TelemetryError } from 'types/telemetryEventNames';
import { parseJson } from 'utils/jsonUtils';
import { clearSupportedResourceKinds } from './kubectlGet';
import { invokeKubectlCommand } from './kubernetesToolsKubectl';
import { ActionOnInvalid } from '@kubernetes/client-node/dist/config_types';

export let currentContextName: string;
export const onKubeConfigChanged = new EventEmitter<k8s.KubeConfig>();
export const onCurrentContextChanged = new EventEmitter<k8s.KubeConfig>();

export const kubeConfig = new k8s.KubeConfig();

/**
* Gets current kubectl config with available contexts and clusters.
*/
export async function getKubectlConfig(): Promise<Errorable<KubernetesConfig>> {
const configShellResult = await invokeKubectlCommand('config view -o json');
type KubeConfigChanges = {
currentContextChanged: boolean;
configChanged: boolean;
};

if (configShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_KUBECTL_CONFIG);
return {
succeeded: false,
error: [shellCodeError(configShellResult)],
};
}
function compareKubeConfigs(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): KubeConfigChanges {
// exportConfig() will omit tokens and certs
const textChanged = kc1.exportConfig() !== kc2.exportConfig();

const context1 = kc1.getContextObject(kc1.getCurrentContext());
const context2 = kc2.getContextObject(kc2.getCurrentContext());

const cluster1 = kc1.getCurrentCluster();
const cluster2 = kc2.getCurrentCluster();

const user1 = kc1.getCurrentUser();
const user2 = kc2.getCurrentUser();

const currentContextChanged = !deepEqual(context1, context2) || !deepEqual(cluster1, cluster2) || !deepEqual(user1, user2);

const kubectlConfig = parseJson(configShellResult.stdout);
return {
succeeded: true,
result: kubectlConfig,
currentContextChanged,
configChanged: textChanged || currentContextChanged,
};
}

/**
* Gets current kubectl context name.
*/
export async function getCurrentContextName(): Promise<Errorable<string>> {
const currentContextShellResult = await invokeKubectlCommand('config current-context');
if (currentContextShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_CURRENT_KUBERNETES_CONTEXT);
console.warn(`Failed to get current kubectl context: ${currentContextShellResult?.stderr}`);
setVSCodeContext(ContextId.NoClusterSelected, true);
return {
succeeded: false,
error: [`${currentContextShellResult?.code || ''} ${currentContextShellResult?.stderr}`],
};
// reload the kubeconfig via kubernetes-tools. fire events if things have changed
export async function loadKubeConfig(force = false) {
const configShellResult = await invokeKubectlCommand('config view');

if (configShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_KUBECTL_CONFIG);
return;
}

const currentContext = currentContextShellResult.stdout.trim();
setVSCodeContext(ContextId.NoClusterSelected, false);
console.log('kc context name', kubeConfig.getCurrentContext());

currentContextName = currentContext;
return {
succeeded: true,
result: currentContext,
};
const newKubeConfig = new k8s.KubeConfig();
newKubeConfig.loadFromString(configShellResult.stdout, {onInvalidEntry: ActionOnInvalid.FILTER});

const kcChanges = compareKubeConfigs(kubeConfig, newKubeConfig);
if (force || kcChanges.configChanged) {
kubeConfig.loadFromString(configShellResult.stdout);

console.log('KubeConfig changed');
onKubeConfigChanged.fire(kubeConfig);

if(force || kcChanges.currentContextChanged) {
console.log('Current Context changed');
onCurrentContextChanged.fire(kubeConfig);
}
}
}


/**
* Sets current kubectl context.
* @param contextName Kubectl context name to use.
* @returns `undefined` in case of an error or Object with information about
* whether or not context was switched or didn't need it (current).
*/
export async function setCurrentContext(contextName: string): Promise<undefined | { isChanged: boolean; }> {
const currentContextResult = await getCurrentContextName();
const currentContextResult = getCurrentContextName();
if (succeeded(currentContextResult) && currentContextResult.result === contextName) {
return {
isChanged: false,
Expand Down Expand Up @@ -98,47 +112,48 @@ export async function setCurrentContext(contextName: string): Promise<undefined
* Also add cluster info to the context objects.
*/
export async function getContexts(): Promise<Errorable<KubernetesContextWithCluster[]>> {
const kubectlConfig = await getKubectlConfig();

if (failed(kubectlConfig)) {
return {
succeeded: false,
error: kubectlConfig.error,
};
}
if (!kubectlConfig.result.contexts) {
return {
succeeded: false,
error: ['Config fetched, but contexts not found.'],
};
}

const contexts: KubernetesContextWithCluster[] = kubectlConfig.result.contexts.map((context: KubernetesContextWithCluster) => {
const clusterInfo = kubectlConfig.result.clusters?.find(cluster => cluster.name === context.context.cluster);
if (clusterInfo) {
context.context.clusterInfo = clusterInfo;
}
return context;
});

return {
succeeded: true,
result: contexts,
};
return {succeeded: false, error: ['Not implemented']};
// const kubectlConfig = await getKubectlConfig();

// if (failed(kubectlConfig)) {
// return {
// succeeded: false,
// error: kubectlConfig.error,
// };
// }
// if (!kubectlConfig.result.contexts) {
// return {
// succeeded: false,
// error: ['Config fetched, but contexts not found.'],
// };
// }

// const contexts: KubernetesContextWithCluster[] = kubectlConfig.result.contexts.map((context: KubernetesContextWithCluster) => {
// const currentContextNameKc = kubectlConfig.result['current-context'];
// context.isCurrentContext = context.name === currentContextNameKc;
// const clusterInfo = kubectlConfig.result.clusters?.find(cluster => cluster.name === context.context.cluster);
// if (clusterInfo) {
// context.context.clusterInfo = clusterInfo;
// }
// return context;
// });

// kubectlConfig.result['current-context'];

// return {
// succeeded: true,
// result: contexts,
// };
}

export async function getClusterName(contextName: string): Promise<string> {
const contexts = await getContexts();
if(contexts.succeeded === true) {
return contexts.result.find(context => context.name === contextName)?.context.clusterInfo?.name || contextName;
} else {
return contextName;
}
const context = kubeConfig.getContextObject(contextName);
return kubeConfig.getCluster(context?.cluster || contextName)?.name ?? contextName;
}

export async function getCurrentContextWithCluster(): Promise<KubernetesContextWithCluster | undefined> {
export async function updateCurrentContextWithCluster(): Promise<KubernetesContextWithCluster | undefined> {
const [contextName, contexts] = await Promise.all([
aresult(getCurrentContextName()),
result(getCurrentContextName()),
aresult(getContexts()),
]);

Expand All @@ -152,3 +167,45 @@ export async function getCurrentContextWithCluster(): Promise<KubernetesContextW
return context;
}


/**
* Gets current kubectl context name.
*/
export function getCurrentContextName(): Errorable<string> {
const name = kubeConfig.getCurrentContext();
if (name) {
return {
succeeded: true,
result: kubeConfig.getCurrentContext(),
};
} else {
return {
succeeded: false,
error: ['No current context'],
};
}
}


// const currentContextShellResult = await invokeKubectlCommand('config current-context');
// if (currentContextShellResult?.code !== 0) {
// telemetry.sendError(TelemetryError.FAILED_TO_GET_CURRENT_KUBERNETES_CONTEXT);
// console.warn(`Failed to get current kubectl context: ${currentContextShellResult?.stderr}`);
// setVSCodeContext(ContextId.NoClusterSelected, true);
// return {
// succeeded: false,
// error: [`${currentContextShellResult?.code || ''} ${currentContextShellResult?.stderr}`],
// };
// }

// const currentContext = currentContextShellResult.stdout.trim();
// setVSCodeContext(ContextId.NoClusterSelected, false);

// currentContextName = currentContext;
// return {
// succeeded: true,
// result: currentContext,
// };
// }


Loading

0 comments on commit 8ad201c

Please sign in to comment.