diff --git a/src/cli/kubernetes/kubectlProxy.ts b/src/cli/kubernetes/kubectlProxy.ts new file mode 100644 index 00000000..ae8561ad --- /dev/null +++ b/src/cli/kubernetes/kubectlProxy.ts @@ -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 = {}; +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; +} + diff --git a/src/cli/kubernetes/kubernetesConfig.ts b/src/cli/kubernetes/kubernetesConfig.ts index aa051124..1ba33641 100644 --- a/src/cli/kubernetes/kubernetesConfig.ts +++ b/src/cli/kubernetes/kubernetesConfig.ts @@ -1,65 +1,79 @@ +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(); +export const onCurrentContextChanged = new EventEmitter(); +export const kubeConfig = new k8s.KubeConfig(); -/** - * Gets current kubectl config with available contexts and clusters. - */ -export async function getKubectlConfig(): Promise> { - 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> { - 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. @@ -67,7 +81,7 @@ export async function getCurrentContextName(): Promise> { * whether or not context was switched or didn't need it (current). */ export async function setCurrentContext(contextName: string): Promise { - const currentContextResult = await getCurrentContextName(); + const currentContextResult = getCurrentContextName(); if (succeeded(currentContextResult) && currentContextResult.result === contextName) { return { isChanged: false, @@ -98,47 +112,48 @@ export async function setCurrentContext(contextName: string): Promise> { - 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 { - 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 { +export async function updateCurrentContextWithCluster(): Promise { const [contextName, contexts] = await Promise.all([ - aresult(getCurrentContextName()), + result(getCurrentContextName()), aresult(getContexts()), ]); @@ -152,3 +167,45 @@ export async function getCurrentContextWithCluster(): Promise { + 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, +// }; +// } + + diff --git a/src/cli/kubernetes/kubernetesConfigWatcher.ts b/src/cli/kubernetes/kubernetesConfigWatcher.ts new file mode 100644 index 00000000..ff34cc54 --- /dev/null +++ b/src/cli/kubernetes/kubernetesConfigWatcher.ts @@ -0,0 +1,88 @@ + +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; + +function hostPath(kcPath: KubernetesToolsConfigurationV1_1.KubeconfigPath): string | undefined { + if(kcPath.pathType === 'host') { + return kcPath.hostPath; + } +} + + +export async function initKubeConfigWatcher() { + const configuration = await kubernetes.extension.configuration.v1_1; + if (!configuration.available) { + return; + } + + kubeConfigPath = hostPath(configuration.api.getKubeconfigPath()); + + await initKubeConfigPathWatcher(); + + configuration.api.onDidChangeContext(context => { + // current context is changed, do something with it + console.log('context changed!', context); + loadKubeConfig(); + }); + + restartFsWatcher(); + + + console.log('watching kubeconfigs'); +} + + +async function initKubeConfigPathWatcher() { + const configuration = await kubernetes.extension.configuration.v1_1; + if (!configuration.available) { + return; + } + + configuration.api.onDidChangeKubeconfigPath(async kcpath => { + const path = hostPath(kcpath); + console.log('path changed event!', path); + // fires twice + if(path !== kubeConfigPath) { + kubeConfigPath = path; + loadKubeConfig(); + + restartFsWatcher(); + } + }); +} + +function restartFsWatcher() { + stopFsWatcher(); + + if(!kubeConfigPath) { + return; + } + + const uri = vscode.Uri.file(kubeConfigPath); + const dirname = Utils.dirname(uri); + const basename = Utils.basename(uri); + + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(dirname, basename)); + watcher.onDidChange(e => { + console.log('kubeconfig file changed!', e); + loadKubeConfig(); + }); +} + +function stopFsWatcher() { + if(fsWacher) { + fsWacher.dispose(); + fsWacher = undefined; + } +} diff --git a/src/cli/kubernetes/kubernetesToolsKubectl.ts b/src/cli/kubernetes/kubernetesToolsKubectl.ts index 7b4b11d8..ad05d92f 100644 --- a/src/cli/kubernetes/kubernetesToolsKubectl.ts +++ b/src/cli/kubernetes/kubernetesToolsKubectl.ts @@ -38,7 +38,7 @@ async function getKubectlApi() { * @param command Kubectl command to run. * @returns Kubectl command results. */ -export async function invokeKubectlCommand(command: string): Promise { +export async function invokeKubectlCommand(command: string, printOutput = true): Promise { const kubectl = await getKubectlApi(); if (!kubectl) { return; @@ -46,23 +46,25 @@ export async function invokeKubectlCommand(command: string): Promise kubectl ${command}`, { - channelName: 'GitOps: kubectl', - newline: 'single', - revealOutputView: false, - }); - - if (kubectlShellResult?.code === 0) { - output.send(kubectlShellResult.stdout, { + if(printOutput) { + output.send(`> kubectl ${command}`, { channelName: 'GitOps: kubectl', + newline: 'single', revealOutputView: false, }); - } else { - output.send(kubectlShellResult?.stderr || '', { - channelName: 'GitOps: kubectl', - revealOutputView: false, - logLevel: 'error', - }); + + if (kubectlShellResult?.code === 0) { + output.send(kubectlShellResult.stdout, { + channelName: 'GitOps: kubectl', + revealOutputView: false, + }); + } else { + output.send(kubectlShellResult?.stderr || '', { + channelName: 'GitOps: kubectl', + revealOutputView: false, + logLevel: 'error', + }); + } } return kubectlShellResult; diff --git a/src/cli/shell/exec.ts b/src/cli/shell/exec.ts index f8d82a00..169af2f3 100644 --- a/src/cli/shell/exec.ts +++ b/src/cli/shell/exec.ts @@ -40,6 +40,7 @@ export const enum Platform { export type ExecCallback = shelljs.ExecCallback; +type ProcCallback = (proc: ChildProcess)=> void; const WINDOWS = 'win32'; export interface ShellResult { @@ -114,9 +115,9 @@ function execOpts({ cwd }: { cwd?: string; } = {}): shelljs.ExecOptions { return opts; } -async function exec(cmd: string, { cwd }: { cwd?: string; } = {}): Promise { +async function exec(cmd: string, { cwd, callback }: { cwd?: string; callback?: ProcCallback;} = {}): Promise { try { - return await execCore(cmd, execOpts({ cwd }), null); + return await execCore(cmd, execOpts({ cwd }), callback); } catch (e) { console.error(e); window.showErrorMessage(String(e)); @@ -199,7 +200,7 @@ async function execWithOutput( } } -function execCore(cmd: string, opts: any, callback?: ((proc: ChildProcess)=> void) | null, stdin?: string): Promise { +function execCore(cmd: string, opts: any, callback?: ProcCallback, stdin?: string): Promise { return new Promise(resolve => { if (getUseWsl()) { cmd = `wsl ${cmd}`; diff --git a/src/commands/setCurrentKubernetesContext.ts b/src/commands/setCurrentKubernetesContext.ts index ef8f7724..d79ccb1c 100644 --- a/src/commands/setCurrentKubernetesContext.ts +++ b/src/commands/setCurrentKubernetesContext.ts @@ -1,4 +1,4 @@ -import { setCurrentContext } from 'cli/kubernetes/kubernetesConfig'; +import { loadKubeConfig, setCurrentContext } from 'cli/kubernetes/kubernetesConfig'; import { ClusterNode } from 'ui/treeviews/nodes/cluster/clusterNode'; import { refreshAllTreeViews } from 'ui/treeviews/treeViews'; @@ -8,6 +8,7 @@ import { refreshAllTreeViews } from 'ui/treeviews/treeViews'; export async function setCurrentKubernetesContext(clusterContext: ClusterNode): Promise { const setContextResult = await setCurrentContext(clusterContext.contextName); if (setContextResult?.isChanged) { + loadKubeConfig(); refreshAllTreeViews(); } } diff --git a/src/data/createKubeProxyConfig.ts b/src/data/createKubeProxyConfig.ts index c7f1b49c..965093c2 100644 --- a/src/data/createKubeProxyConfig.ts +++ b/src/data/createKubeProxyConfig.ts @@ -1,33 +1,19 @@ import * as k8s from '@kubernetes/client-node'; import { ActionOnInvalid } from '@kubernetes/client-node/dist/config_types'; -import { kubeConfigPath } from './kubeConfigWatcher'; +import { kubeConfig } from 'cli/kubernetes/kubernetesConfig'; -function loadDefaultKubeConfig(): k8s.KubeConfig { - const kc = new k8s.KubeConfig(); - const opts = {onInvalidEntry: ActionOnInvalid.FILTER}; - const kcFilePath = kubeConfigPath(); - if(kcFilePath) { - kc.loadFromFile(kcFilePath, opts); - } else { - kc.loadFromDefault(); - } - - return kc; -} export function createKubeProxyConfig(port: number): k8s.KubeConfig { - const kcDefault = loadDefaultKubeConfig(); - const cluster = { - name: kcDefault.getCurrentCluster()?.name, + name: kubeConfig.getCurrentCluster()?.name, server: `http://127.0.0.1:${port}`, }; - const user = kcDefault.getCurrentUser(); + const user = kubeConfig.getCurrentUser(); const context = { - name: kcDefault.getCurrentContext(), + name: kubeConfig.getCurrentContext(), user: user?.name, cluster: cluster.name, }; diff --git a/src/data/fluxInformer.ts b/src/data/fluxInformer.ts index 2de72a1f..71597269 100644 --- a/src/data/fluxInformer.ts +++ b/src/data/fluxInformer.ts @@ -18,9 +18,8 @@ import * as k8s from '@kubernetes/client-node'; import { GitRepository } from 'types/flux/gitRepository'; import { Kind, KubernetesListObject, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; -import { createKubeProxyConfig } from './createKubeProxyConfig'; -import { initKubeProxy, kubeProxyPort } from './informerKubeProxy'; -import { initKubeConfigWatcher } from './kubeConfigWatcher'; +// import { createKubeProxyConfig } from './createKubeProxyConfig'; +// import { initKubeConfigWatcher } from '../cli/kubernetes/kubernetesConfigWatcher'; @@ -66,47 +65,53 @@ async function informerKeepAlive(informer: k8s.Infor export let informer: k8s.Informer & k8s.ObjectCache | undefined; export async function initFluxInformers(eventFn?: InformerEventFunc) { - await initKubeConfigWatcher(() => {}); + + // await initKubeConfigWatcher(() => { + // restartKubeProxy(); + // const contextName = aresult(getCurrentContextName()); + // console.log('kubeconfig changed event!', contextName); + // }); // initKubeProxy(); +} - // await createFluxInformer(); - // setInterval(() => createFluxInformer(), 1000); +// await createFluxInformer(); +// setInterval(() => createFluxInformer(), 1000); - // // DEBUG - // setInterval(() => { - // if(informer) { - // console.log('+Informer exists: ', Date().slice(19, 24), informer.list()); - // } else { - // console.log('!No Informer: ', Date().slice(19, 24)); - // } - // }, 1500); -} +// // DEBUG +// setInterval(() => { +// if(informer) { +// console.log('+Informer exists: ', Date().slice(19, 24), informer.list()); +// } else { +// console.log('!No Informer: ', Date().slice(19, 24)); +// } +// }, 1500); + export async function createFluxInformer() { // running already or no proxy - if(informer || !kubeProxyPort) { - return; - } + // if(informer || !kubeProxyPort) { + // return; + // } - const kc = createKubeProxyConfig(kubeProxyPort); - console.log('starting informer...'); + // const kc = createKubeProxyConfig(kubeProxyPort); + // console.log('starting informer...'); - informer = await startInformer(Kind.GitRepository, kc); - if(!informer) { - console.log('failed to start informer'); - return; - } + // informer = await startInformer(Kind.GitRepository, kc); + // if(!informer) { + // console.log('failed to start informer'); + // return; + // } - informer.on('error', (err: any) => { - console.error('informer error event', err); - if(informer) { - informer.stop(); - } - informer = undefined; - }); + // informer.on('error', (err: any) => { + // console.error('informer error event', err); + // if(informer) { + // informer.stop(); + // } + // informer = undefined; + // }); - console.log('informer started'); + // console.log('informer started'); } @@ -158,4 +163,5 @@ async function startInformer(kind: Kind, kubeConfig: k8s.KubeConfig) { // sourceDataProvider.update(obj); // sourceDataProvider.delete(obj); -// \ No newline at end of file +// + diff --git a/src/data/informerKubeProxy.ts b/src/data/informerKubeProxy.ts deleted file mode 100644 index 66c0c85a..00000000 --- a/src/data/informerKubeProxy.ts +++ /dev/null @@ -1,27 +0,0 @@ -export let kubeProxyPort: number | undefined; - -// export const fluxInformers: Record = {}; -export async function initKubeProxy() { - await startKubeProxy(); - setInterval(() => { - if(!kubeProxyPort) { - startKubeProxy(); - } - }, 1000); -} - - - -export async function startKubeProxy() { - kubeProxyPort = 57375; -} - -export function stopKubeProxy(): void { - kubeProxyPort = undefined; -} - - - - - - diff --git a/src/data/informerPool.ts b/src/data/informerPool.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/data/kubeConfigWatcher.ts b/src/data/kubeConfigWatcher.ts deleted file mode 100644 index 4474a10c..00000000 --- a/src/data/kubeConfigWatcher.ts +++ /dev/null @@ -1,114 +0,0 @@ - -import * as kubernetes from 'vscode-kubernetes-tools-api'; -import * as vscode from 'vscode'; -import { Utils } from 'vscode-uri'; -import deepEqual from 'lite-deep-equal'; -import { ConfigurationV1_1 as KubernetesToolsConfigurationV1_1} from 'vscode-kubernetes-tools-api/js/configuration/v1_1'; - - -// import { ConfigurationV1_1 } from 'vscode-kubernetes-tools-api/js/configuration/v1_1'; -import { get } from 'http'; -import { getContexts, getCurrentContextName, getCurrentContextWithCluster, getKubectlConfig } from 'cli/kubernetes/kubernetesConfig'; -import { getCurrentClusterInfo } from 'ui/treeviews/treeViews'; -import { aresult, failed, result, results } from 'types/errorable'; -import { KubernetesCluster, KubernetesContext, KubernetesContextWithCluster } from 'types/kubernetes/kubernetesConfig'; - - -// type KubeConfigUniqueParams = { -// contextName: string; -// clusterName: string; -// clusterServer: string; -// userName: string; -// }; - -let fsWacher: vscode.FileSystemWatcher | undefined; -let selectedKubeConfigPath: KubernetesToolsConfigurationV1_1.KubeconfigPath | undefined; -let contextWithCluster: KubernetesContextWithCluster | undefined; - -export function kubeConfigPath(): string | undefined { - if(selectedKubeConfigPath && selectedKubeConfigPath.pathType === 'host') { - return selectedKubeConfigPath.hostPath; - } -} - - -export async function initKubeConfigWatcher(changedFn: ()=> void) { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - - selectedKubeConfigPath = configuration.api.getKubeconfigPath(); - contextWithCluster = await getCurrentContextWithCluster(); - - await initKubeConfigPathWatcher(); - console.log('watching kubeconfigs'); -} - - -// when the user changes the KUBECONFIG path -// using the kubernetes tools extension -// let previousKubeConfigPath: k8s.ConfigurationV1_1.KubeconfigPath | undefined; - -async function initKubeConfigPathWatcher() { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - - configuration.api.onDidChangeKubeconfigPath(path => { - // fires twice - console.log('path changed!', path); - if(path !== selectedKubeConfigPath) { - selectedKubeConfigPath = path; - // TODO: recreate file watcher - - if(path.pathType === 'host') { - stopFsWatcher(); - startFsWatcher(path.hostPath); - } else { - // disable file watcher for WSL for now - console.error('WSL not yet supported'); - stopFsWatcher(); - } - } - }); - - - configuration.api.onDidChangeContext((context) => { - // current context is changed, do something with it - console.log('context changed!', context); - }); - - - -} - -function startFsWatcher(path: string) { - const uri = vscode.Uri.file(path); - const dirname = Utils.dirname(uri); - const basename = Utils.basename(uri); - // const data = await vscode.workspace.fs.readFile(uri); - // console.log(data); - - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(dirname, basename)); - watcher.onDidChange((e) => { - console.log('kubeconfig file changed!', e); - }); -} - -function stopFsWatcher() { - if(fsWacher) { - fsWacher.dispose(); - fsWacher = undefined; - } -} - - -async function initKubeConfigFileWatcher() { - const configuration = await kubernetes.extension.configuration.v1_1; - if (!configuration.available) { - return; - } - -} diff --git a/src/extension.ts b/src/extension.ts index 5e050d2e..bd9a457e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,9 @@ import { TelemetryEvent } from './types/telemetryEventNames'; import { promptToInstallFlux } from './ui/promptToInstallFlux'; import { statusBar } from './ui/statusBar'; import { clusterTreeViewProvider, createTreeViews, sourceTreeViewProvider, templateTreeViewProvider, workloadTreeViewProvider } from './ui/treeviews/treeViews'; +import { initKubeConfigWatcher } from 'cli/kubernetes/kubernetesConfigWatcher'; +import { loadKubeConfig } from 'cli/kubernetes/kubernetesConfig'; +import { initKubeProxy } from 'cli/kubernetes/kubectlProxy'; /** Disable interactive modal dialogs, useful for testing */ export let disableConfirmations = false; @@ -37,16 +40,20 @@ export let telemetry: Telemetry | any; export async function activate(context: ExtensionContext) { // Keep a reference to the extension context extensionContext = context; - listenConfigChanged(); + listenExtensionConfigChanged(); globalState = new GlobalState(context); telemetry = new Telemetry(context, getExtensionVersion(), GitOpsExtensionConstants.ExtensionId); // create gitops tree views - createTreeViews(); + // createTreeViews(); // await startFluxInformers(sourceTreeViewProvider, workloadTreeViewProvider, templateTreeViewProvider); - await initFluxInformers(); + // await initFluxInformers(); + await loadKubeConfig(); + + await initKubeConfigWatcher(); + await initKubeProxy(); // register gitops commands registerCommands(context); @@ -92,7 +99,7 @@ export async function activate(context: ExtensionContext) { return api; } -function listenConfigChanged() { +function listenExtensionConfigChanged() { workspace.onDidChangeConfiguration(async e => { if(!e.affectsConfiguration('gitops')) { return; diff --git a/src/types/kubernetes/kubernetesConfig.ts b/src/types/kubernetes/kubernetesConfig.ts index 3be7104d..751e47ab 100644 --- a/src/types/kubernetes/kubernetesConfig.ts +++ b/src/types/kubernetes/kubernetesConfig.ts @@ -18,11 +18,9 @@ export interface KubernetesConfig { */ export interface KubernetesCluster { readonly name: string; - readonly cluster: { - readonly server: string; - readonly 'certificate-authority'?: string; - readonly 'certificate-authority-data'?: string; - }; + readonly server: string; + readonly 'certificate-authority'?: string; + readonly 'certificate-authority-data'?: string; } /** @@ -30,6 +28,7 @@ export interface KubernetesCluster { */ export interface KubernetesContext { readonly name: string; + isCurrentContext?: boolean; readonly context: { readonly cluster: string; readonly user: string; diff --git a/src/ui/treeviews/dataProviders/dataProvider.ts b/src/ui/treeviews/dataProviders/dataProvider.ts index 47349482..0ebf89d1 100644 --- a/src/ui/treeviews/dataProviders/dataProvider.ts +++ b/src/ui/treeviews/dataProviders/dataProvider.ts @@ -1,6 +1,7 @@ import { KubernetesObject } from 'types/kubernetes/kubernetesTypes'; import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode'; import { TreeNode } from '../nodes/treeNode'; +import { loadKubeConfig } from 'cli/kubernetes/kubernetesConfig'; /** * Defines tree view data provider base class for all GitOps tree views. @@ -14,10 +15,12 @@ export class DataProvider implements TreeDataProvider { * Reloads tree view item and its children. * @param treeItem Tree item to refresh. */ - public refresh(treeItem?: TreeItem) { + public async refresh(treeItem?: TreeItem) { if (!treeItem) { // Only clear all root nodes when no node was passed this.treeItems = null; + await loadKubeConfig(true); + } this._onDidChangeTreeData.fire(treeItem); } diff --git a/src/ui/treeviews/nodes/cluster/clusterNode.ts b/src/ui/treeviews/nodes/cluster/clusterNode.ts index b36e15b8..79b2f95a 100644 --- a/src/ui/treeviews/nodes/cluster/clusterNode.ts +++ b/src/ui/treeviews/nodes/cluster/clusterNode.ts @@ -2,7 +2,7 @@ import { ExtensionMode, MarkdownString } from 'vscode'; import { fluxVersion } from 'cli/checkVersions'; import { detectClusterProvider, isGitOpsEnabled } from 'cli/kubernetes/clusterProvider'; -import { currentContextName } from 'cli/kubernetes/kubernetesConfig'; +// import { currentContextName } from 'cli/kubernetes/kubernetesConfig'; import { extensionContext, globalState, setVSCodeContext } from 'extension'; import { CommandId, ContextId } from 'types/extensionIds'; import { ClusterProvider } from 'types/kubernetes/clusterProvider'; @@ -10,6 +10,8 @@ import { KubernetesCluster, KubernetesContextWithCluster } from 'types/kubernete import { NodeContext } from 'types/nodeContext'; import { createMarkdownHr, createMarkdownTable } from 'utils/markdownUtils'; import { TreeNode } from '../treeNode'; +import { getCurrentContextName } from 'cli/kubernetes/kubernetesConfig'; +import { result } from 'types/errorable'; /** * Defines Cluster context tree view item for displaying @@ -71,7 +73,7 @@ export class ClusterNode extends TreeNode { this.clusterContext = kubernetesContext; this.clusterName = kubernetesContext.context.clusterInfo?.name || kubernetesContext.name; this.contextName = kubernetesContext.name; - this.description = kubernetesContext.context.clusterInfo?.cluster.server; + this.description = kubernetesContext.context.clusterInfo?.server; this.setIcon('cloud'); } @@ -114,7 +116,7 @@ export class ClusterNode extends TreeNode { const markdown: MarkdownString = createMarkdownTable(cluster); createMarkdownHr(markdown); - if(this.contextName === currentContextName) { + if(this.contextName === result(getCurrentContextName())) { markdown.appendMarkdown(`Flux Version: ${fluxVersion}`); } @@ -142,19 +144,19 @@ export class ClusterNode extends TreeNode { } get contexts() { - const result = [NodeContext.Cluster]; + const cs = [NodeContext.Cluster]; if (typeof this.isGitOpsEnabled === 'boolean') { - result.push( + cs.push( this.isGitOpsEnabled ? NodeContext.ClusterGitOpsEnabled : NodeContext.ClusterGitOpsNotEnabled, ); } - result.push( + cs.push( this.isCurrent ? NodeContext.CurrentCluster : NodeContext.NotCurrentCluster, ); - return result; + return cs; } } diff --git a/src/ui/treeviews/treeViews.ts b/src/ui/treeviews/treeViews.ts index dca78a5c..6a9cff62 100644 --- a/src/ui/treeviews/treeViews.ts +++ b/src/ui/treeviews/treeViews.ts @@ -13,7 +13,7 @@ import { ClusterNode } from './nodes/cluster/clusterNode'; import { TreeNode } from './nodes/treeNode'; import { detectClusterProvider } from 'cli/kubernetes/clusterProvider'; -import { getClusterName, getCurrentContextName } from 'cli/kubernetes/kubernetesConfig'; +import { getClusterName, getCurrentContextName, loadKubeConfig } from 'cli/kubernetes/kubernetesConfig'; import { ClusterInfo } from 'types/kubernetes/clusterProvider'; import { TemplateDataProvider } from './dataProviders/templateDataProvider'; diff --git a/src/utils/markdownUtils.ts b/src/utils/markdownUtils.ts index 3498ba3b..ddc6a2d9 100644 --- a/src/utils/markdownUtils.ts +++ b/src/utils/markdownUtils.ts @@ -30,9 +30,9 @@ export function createMarkdownTable(kubernetesObject: KnownTreeNodeResources): M if ('context' in kubernetesObject) { createMarkdownTableRow('context name', kubernetesObject.name, markdown); createMarkdownTableRow('cluster name', kubernetesObject.context.clusterInfo?.name, markdown); - createMarkdownTableRow('cluster.server', kubernetesObject.context.clusterInfo?.cluster?.server, markdown); - createMarkdownTableRow('cluster.certificate-authority', kubernetesObject.context.clusterInfo?.cluster?.['certificate-authority'], markdown); - createMarkdownTableRow('cluster.certificate-authority-data', kubernetesObject.context.clusterInfo?.cluster?.['certificate-authority-data'], markdown); + createMarkdownTableRow('cluster.server', kubernetesObject.context.clusterInfo?.server, markdown); + createMarkdownTableRow('cluster.certificate-authority', kubernetesObject.context.clusterInfo?.['certificate-authority'], markdown); + createMarkdownTableRow('cluster.certificate-authority-data', kubernetesObject.context.clusterInfo?.['certificate-authority-data'], markdown); return markdown; }