Skip to content

Commit

Permalink
existing treeviews work with new kubeconfig and events
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 8ad201c commit 32ed39b
Show file tree
Hide file tree
Showing 26 changed files with 191 additions and 314 deletions.
11 changes: 6 additions & 5 deletions src/cli/azure/azureTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getCurrentClusterInfo, refreshAllTreeViews } from 'ui/treeviews/treeVie
import { parseJson } from 'utils/jsonUtils';
import { checkAzurePrerequisites } from './azurePrereqs';
import { getAzureMetadata } from './getAzureMetadata';
import { kubeConfig } from 'cli/kubernetes/kubernetesConfig';

export type AzureClusterProvider = ClusterProvider.AKS | ClusterProvider.AzureARC;

Expand Down Expand Up @@ -127,16 +128,16 @@ class AzureTools {
}

async enableGitOpsGeneric(contextName: string) {
const currentClusterInfo = await getCurrentClusterInfo();
if (failed(currentClusterInfo)) {
const context = kubeConfig.getContextObject(contextName);
if (!context) {
return;
}

const clusterName = currentClusterInfo.result.clusterName;
const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterName) || {};
const clusterName = context.cluster;
const clusterMetadata: ClusterMetadata = globalState.getClusterMetadata(clusterName || contextName) || {};

clusterMetadata.clusterProvider = ClusterProvider.Generic;
globalState.setClusterMetadata(clusterName, clusterMetadata);
globalState.setClusterMetadata(clusterName || contextName, clusterMetadata);
refreshAllTreeViews();
await fluxTools.install(contextName);
}
Expand Down
5 changes: 3 additions & 2 deletions src/cli/azure/getAzureMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import safesh from 'shell-escape-tag';
import { QuickPickItem, window } from 'vscode';

import { getClusterName } from 'cli/kubernetes/kubernetesConfig';
import { invokeKubectlCommand } from 'cli/kubernetes/kubernetesToolsKubectl';
import { ShellResult, shell } from 'cli/shell/exec';
import { ClusterProvider } from 'types/kubernetes/clusterProvider';
import { ConfigMap } from 'types/kubernetes/kubernetesTypes';
import { parseJson } from 'utils/jsonUtils';
import { AzureClusterProvider, AzureConstants } from './azureTools';
import { kubeConfig } from 'cli/kubernetes/kubernetesConfig';

export interface AzureMetadata {
resourceGroup: string;
Expand Down Expand Up @@ -57,7 +57,8 @@ export async function getAzureMetadata(contextName: string, clusterProvider: Azu
return metadata;
}

const clusterName = await getClusterName(contextName);
const context = kubeConfig.getContextObject(contextName)!;
const clusterName = context.cluster || contextName;
return await getAzCliOrUserMetadata(clusterName);
}

Expand Down
11 changes: 6 additions & 5 deletions src/cli/kubernetes/clusterProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,31 @@ import { ConfigMap, Node } from 'types/kubernetes/kubernetesTypes';
import { TelemetryError } from 'types/telemetryEventNames';
import { parseJson, parseJsonItems } from 'utils/jsonUtils';
import { getFluxControllers, notAnErrorServerNotRunning } from './kubectlGet';
import { getClusterName } from './kubernetesConfig';
import { invokeKubectlCommand } from './kubernetesToolsKubectl';
import { kubeConfig } from './kubernetesConfig';

/**
* Try to detect known cluster providers. Returns user selected cluster type if that is set.
* @param context target context to get resources from.
* TODO: maybe use Errorable?
*/
export async function detectClusterProvider(context: string): Promise<ClusterProvider> {
const clusterName = await getClusterName(context);
export async function detectClusterProvider(contextName: string): Promise<ClusterProvider> {
const context = kubeConfig.getContextObject(contextName)!;
const clusterName = context.cluster || contextName;
const clusterMetadata = globalState.getClusterMetadata(clusterName);

if(clusterMetadata?.clusterProvider) {
return clusterMetadata.clusterProvider;
}

const tryProviderAzureARC = await isClusterAzureARC(context);
const tryProviderAzureARC = await isClusterAzureARC(contextName);
if (tryProviderAzureARC === ClusterProvider.AzureARC) {
return ClusterProvider.AzureARC;
} else if (tryProviderAzureARC === ClusterProvider.DetectionFailed) {
return ClusterProvider.DetectionFailed;
}

const tryProviderAKS = await isClusterAKS(context);
const tryProviderAKS = await isClusterAKS(contextName);
if (tryProviderAKS === ClusterProvider.AKS) {
return ClusterProvider.AKS;
} else if (tryProviderAKS === ClusterProvider.DetectionFailed) {
Expand Down
39 changes: 23 additions & 16 deletions src/cli/kubernetes/kubectlProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,42 @@ import { ChildProcess } from 'child_process';
export let kubeProxyConfig: k8s.KubeConfig | undefined;
let kubectlProxyProcess: ChildProcess | undefined;

// export const fluxInformers: Record<string, FluxInformer> = {};
// tries to keep alive the `kubectl proxy` process
// if process dies or errors out it will be stopped
// and restarted by the 1000ms interval
// if context changes proxy is stopped and restarted immediately
export function initKubeProxy() {
restartKubeProxy();
startKubeProxy();

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

// user switched kubeconfig context
onCurrentContextChanged.event(() => {
restartKubeProxy();
// user switched kubeconfig context.
onCurrentContextChanged.event(async () => {
if(kubectlProxyProcess) {
await stopKubeProxy();
}
await startKubeProxy();
});
}

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

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

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

p.stdout?.on('data', (data: string) => {
Expand All @@ -49,23 +56,23 @@ function procStarted(p: ChildProcess) {

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


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

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

async function stopKubeProxy() {
// kubeProxyPort = undefined;
kubeProxyConfig = undefined;
if(kubectlProxyProcess) {
kubectlProxyProcess.kill();
if(!kubectlProxyProcess.killed) {
kubectlProxyProcess.kill();
}
kubectlProxyProcess = undefined;
}
}
Expand Down
117 changes: 24 additions & 93 deletions src/cli/kubernetes/kubernetesConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ import safesh from 'shell-escape-tag';
import { EventEmitter, window } from 'vscode';

import * as k8s from '@kubernetes/client-node';
import { ActionOnInvalid } from '@kubernetes/client-node/dist/config_types';
import { shellCodeError } from 'cli/shell/exec';
import { setVSCodeContext, telemetry } from 'extension';
import { Errorable, aresult, failed, result, succeeded } from 'types/errorable';
import { Errorable, 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 { loadKubeConfigPath } from './kubernetesConfigWatcher';
import { invokeKubectlCommand } from './kubernetesToolsKubectl';
import { ActionOnInvalid } from '@kubernetes/client-node/dist/config_types';

export const onKubeConfigChanged = new EventEmitter<k8s.KubeConfig>();
export const onKubeConfigContextsChanged = new EventEmitter<k8s.KubeConfig>();
export const onCurrentContextChanged = new EventEmitter<k8s.KubeConfig>();

export const kubeConfig = new k8s.KubeConfig();
export const kubeConfig: k8s.KubeConfig = new k8s.KubeConfig();

type KubeConfigChanges = {
currentContextChanged: boolean;
configChanged: boolean;
contextsChanged: boolean;
kubeConfigTextChanged: boolean;
};

function compareKubeConfigs(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): KubeConfigChanges {
Expand All @@ -39,18 +39,27 @@ function compareKubeConfigs(kc1: k8s.KubeConfig, kc2: k8s.KubeConfig): KubeConfi

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

const contexts1 = kc1.getContexts();
const contexts2 = kc2.getContexts();

const contextsChanged = !deepEqual(contexts1, contexts2);

return {
currentContextChanged,
configChanged: textChanged || currentContextChanged,
// if current context user or server changed, we need to reload the contexts list
contextsChanged: contextsChanged || currentContextChanged,
kubeConfigTextChanged: textChanged,
};
}

// reload the kubeconfig via kubernetes-tools. fire events if things have changed
export async function loadKubeConfig(force = false) {
export async function loadKubeConfig() {
const configShellResult = await invokeKubectlCommand('config view');

if (configShellResult?.code !== 0) {
telemetry.sendError(TelemetryError.FAILED_TO_GET_KUBECTL_CONFIG);
const path = await loadKubeConfigPath();
window.showErrorMessage(`Failed to load kubeconfig: ${path} ${shellCodeError(configShellResult)}`);
return;
}

Expand All @@ -60,13 +69,14 @@ export async function loadKubeConfig(force = false) {
newKubeConfig.loadFromString(configShellResult.stdout, {onInvalidEntry: ActionOnInvalid.FILTER});

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

console.log('KubeConfig changed');
onKubeConfigChanged.fire(kubeConfig);
if(kcChanges.contextsChanged) {
onKubeConfigContextsChanged.fire(kubeConfig);
}

if(force || kcChanges.currentContextChanged) {
if(kcChanges.currentContextChanged) {
console.log('Current Context changed');
onCurrentContextChanged.fire(kubeConfig);
}
Expand All @@ -81,8 +91,7 @@ export async function loadKubeConfig(force = false) {
* whether or not context was switched or didn't need it (current).
*/
export async function setCurrentContext(contextName: string): Promise<undefined | { isChanged: boolean; }> {
const currentContextResult = getCurrentContextName();
if (succeeded(currentContextResult) && currentContextResult.result === contextName) {
if (kubeConfig.getCurrentContext() === contextName) {
return {
isChanged: false,
};
Expand All @@ -107,84 +116,6 @@ export async function setCurrentContext(contextName: string): Promise<undefined
};
}

/**
* Get a list of contexts from kubeconfig.
* Also add cluster info to the context objects.
*/
export async function getContexts(): Promise<Errorable<KubernetesContextWithCluster[]>> {
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 context = kubeConfig.getContextObject(contextName);
return kubeConfig.getCluster(context?.cluster || contextName)?.name ?? contextName;
}

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

if(!contextName || !contexts) {
return;
}

// const contexts = result(contextsResults);
const context = contexts?.find(c => c.name === contextName);

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');
Expand Down
14 changes: 10 additions & 4 deletions src/cli/kubernetes/kubernetesConfigWatcher.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@

import deepEqual from 'lite-deep-equal';
import * as vscode from 'vscode';
import * as kubernetes from 'vscode-kubernetes-tools-api';
import { ConfigurationV1_1 as KubernetesToolsConfigurationV1_1 } from 'vscode-kubernetes-tools-api/js/configuration/v1_1';
import { Utils } from 'vscode-uri';

// import { getCurrentContextWithCluster } from 'cli/kubernetes/kubernetesConfig';
import { KubernetesContextWithCluster } from 'types/kubernetes/kubernetesConfig';
import { loadKubeConfig } from './kubernetesConfig';


let fsWacher: vscode.FileSystemWatcher | undefined;
let kubeConfigPath: string | undefined;

export async function loadKubeConfigPath(): Promise<string | undefined> {
const configuration = await kubernetes.extension.configuration.v1_1;
if (!configuration.available) {
return;
}

return hostPath(configuration.api.getKubeconfigPath());
}

function hostPath(kcPath: KubernetesToolsConfigurationV1_1.KubeconfigPath): string | undefined {
if(kcPath.pathType === 'host') {
return kcPath.hostPath;
Expand All @@ -26,7 +32,7 @@ export async function initKubeConfigWatcher() {
return;
}

kubeConfigPath = hostPath(configuration.api.getKubeconfigPath());
kubeConfigPath = await loadKubeConfigPath();

await initKubeConfigPathWatcher();

Expand Down
Loading

0 comments on commit 32ed39b

Please sign in to comment.