diff --git a/extensions/kube-context/src/extension.ts b/extensions/kube-context/src/extension.ts index 9a36a28945c..5ab15c25791 100644 --- a/extensions/kube-context/src/extension.ts +++ b/extensions/kube-context/src/extension.ts @@ -25,9 +25,7 @@ interface KubeContext { } const menuItemsRegistered: extensionApi.Disposable[] = []; - -let kubeConfigWatcher: extensionApi.FileSystemWatcher; -let kubeconfigFile: string; +let kubeconfigFile: string | undefined; async function deleteContext(): Promise { // remove the context from the list @@ -78,30 +76,7 @@ async function updateContext(extensionContext: extensionApi.ExtensionContext, ku extensionContext.subscriptions.push(subscription); } -function setupWatcher( - extensionContext: extensionApi.ExtensionContext, - kubeconfigFile: string, -): extensionApi.FileSystemWatcher { - // monitor the kube config file for changes - const kubeConfigWatcher = extensionApi.fs.createFileSystemWatcher(kubeconfigFile); - - // update the tray everytime .kube/config file is updated - kubeConfigWatcher.onDidChange(() => { - updateContext(extensionContext, kubeconfigFile); - }); - - kubeConfigWatcher.onDidCreate(() => { - updateContext(extensionContext, kubeconfigFile); - }); - - kubeConfigWatcher.onDidDelete(() => { - deleteContext(); - }); - - return kubeConfigWatcher; -} - -function getKubeconfig(): string { +function getKubeconfig(): string | undefined { return kubeconfigFile; } @@ -110,21 +85,24 @@ export async function activate(extensionContext: extensionApi.ExtensionContext): // grab current file const kubeconfigUri = await extensionApi.kubernetes.getKubeconfig(); + kubeconfigFile = kubeconfigUri.fsPath; - // setup watcher - kubeConfigWatcher = setupWatcher(extensionContext, kubeconfigFile); - extensionContext.subscriptions.push(kubeConfigWatcher); + // if path exists, update context + if (fs.existsSync(kubeconfigFile)) { + updateContext(extensionContext, kubeconfigFile); + } - extensionApi.kubernetes.onDidChangeKubeconfig((event: extensionApi.KubeConfigChangeEvent) => { - kubeconfigFile = event.newLocation.fsPath; - - // dispose the old watcher - kubeConfigWatcher.dispose(); - - // setup new watcher - kubeConfigWatcher = setupWatcher(extensionContext, kubeconfigFile); - extensionContext.subscriptions.push(kubeConfigWatcher); + // update context menu on change + extensionApi.kubernetes.onDidUpdateKubeconfig((event: extensionApi.KubeconfigUpdateEvent) => { + // update the tray everytime .kube/config file is updated + if (event.type === 'UPDATE' || event.type === 'CREATE') { + kubeconfigFile = event.location.fsPath; + updateContext(extensionContext, kubeconfigFile); + } else if (event.type === 'DELETE') { + deleteContext(); + kubeconfigFile = undefined; + } }); const command = extensionApi.commands.registerCommand('kubecontext.switch', async (newContext: string) => { diff --git a/packages/extension-api/src/extension-api.d.ts b/packages/extension-api/src/extension-api.d.ts index c70f0b78f99..3d9d33787a7 100644 --- a/packages/extension-api/src/extension-api.d.ts +++ b/packages/extension-api/src/extension-api.d.ts @@ -653,14 +653,14 @@ declare module '@tmpwip/extension-api' { export namespace kubernetes { // Path to the configuration file export function getKubeconfig(): Uri; - export const onDidChangeKubeconfig: Event; + export const onDidUpdateKubeconfig: Event; export function setKubeconfig(kubeconfig: Uri): Promise; } /** - * An event describing the change in kubeconfig location + * An event describing the update in kubeconfig location */ - export interface KubeConfigChangeEvent { - readonly oldLocation: Uri; - readonly newLocation: Uri; + export interface KubeconfigUpdateEvent { + readonly type: 'CREATE' | 'UPDATE' | 'DELETE'; + readonly location: Uri; } } diff --git a/packages/main/src/plugin/extension-loader.ts b/packages/main/src/plugin/extension-loader.ts index de28fb98aca..f07bb78931e 100644 --- a/packages/main/src/plugin/extension-loader.ts +++ b/packages/main/src/plugin/extension-loader.ts @@ -35,7 +35,7 @@ import type { NotificationImpl } from './notification-impl'; import { StatusBarItemImpl } from './statusbar/statusbar-item'; import type { StatusBarRegistry } from './statusbar/statusbar-registry'; import { StatusBarAlignLeft, StatusBarAlignRight, StatusBarItemDefaultPriority } from './statusbar/statusbar-item'; -import { FilesystemMonitoring } from './filesystem-monitoring'; +import type { FilesystemMonitoring } from './filesystem-monitoring'; import { Uri } from './types/uri'; import type { KubernetesClient } from './kubernetes-client'; @@ -67,7 +67,6 @@ export class ExtensionLoader { private activatedExtensions = new Map(); private analyzedExtensions = new Map(); private extensionsStoragePath = ''; - private fileSystemMonitoring: FilesystemMonitoring; constructor( private commandRegistry: CommandRegistry, @@ -82,9 +81,8 @@ export class ExtensionLoader { private notifications: NotificationImpl, private statusBarRegistry: StatusBarRegistry, private kubernetesClient: KubernetesClient, - ) { - this.fileSystemMonitoring = new FilesystemMonitoring(); - } + private fileSystemMonitoring: FilesystemMonitoring, + ) {} async listExtensions(): Promise { return Array.from(this.analyzedExtensions.values()).map(extension => ({ @@ -393,8 +391,8 @@ export class ExtensionLoader { async setKubeconfig(kubeconfig: containerDesktopAPI.Uri): Promise { return kubernetesClient.setKubeconfig(kubeconfig); }, - onDidChangeKubeconfig: (listener, thisArg, disposables) => { - return kubernetesClient.onDidChangeKubeconfig(listener, thisArg, disposables); + onDidUpdateKubeconfig: (listener, thisArg, disposables) => { + return kubernetesClient.onDidUpdateKubeconfig(listener, thisArg, disposables); }, }; diff --git a/packages/main/src/plugin/index.ts b/packages/main/src/plugin/index.ts index d6083135fb4..78f56be9b8c 100644 --- a/packages/main/src/plugin/index.ts +++ b/packages/main/src/plugin/index.ts @@ -75,6 +75,7 @@ import { KubernetesClient } from './kubernetes-client'; import type { V1Pod, V1ConfigMap, V1NamespaceList, V1PodList, V1Service } from '@kubernetes/client-node'; import type { V1Route } from './api/openshift-types'; import type { NetworkInspectInfo } from './api/network-info'; +import { FilesystemMonitoring } from './filesystem-monitoring'; type LogType = 'log' | 'warn' | 'trace' | 'debug' | 'error'; export class PluginSystem { @@ -208,7 +209,9 @@ export class PluginSystem { const providerRegistry = new ProviderRegistry(apiSender, containerProviderRegistry, telemetry); const trayMenuRegistry = new TrayMenuRegistry(this.trayMenu, commandRegistry, providerRegistry, telemetry); const statusBarRegistry = new StatusBarRegistry(apiSender); - const kubernetesClient = new KubernetesClient(configurationRegistry); + const fileSystemMonitoring = new FilesystemMonitoring(); + + const kubernetesClient = new KubernetesClient(configurationRegistry, fileSystemMonitoring); await kubernetesClient.init(); const closeBehaviorConfiguration = new CloseBehavior(configurationRegistry, providerRegistry); await closeBehaviorConfiguration.init(); @@ -242,6 +245,7 @@ export class PluginSystem { new NotificationImpl(), statusBarRegistry, kubernetesClient, + fileSystemMonitoring, ); const contributionManager = new ContributionManager(apiSender); diff --git a/packages/main/src/plugin/kubernetes-client.ts b/packages/main/src/plugin/kubernetes-client.ts index 8fbcca20441..8fc85680063 100644 --- a/packages/main/src/plugin/kubernetes-client.ts +++ b/packages/main/src/plugin/kubernetes-client.ts @@ -27,6 +27,7 @@ import { homedir } from 'node:os'; import { resolve } from 'node:path'; import { existsSync } from 'node:fs'; import type { ConfigurationRegistry, IConfigurationNode } from './configuration-registry'; +import type { FilesystemMonitoring } from './filesystem-monitoring'; /** * Handle calls to kubernetes API @@ -41,11 +42,17 @@ export class KubernetesClient { private currrentNamespace: string | undefined; private currentContextName: string | undefined; - private readonly _onDidChangeKubeconfig = new Emitter(); - readonly onDidChangeKubeconfig: containerDesktopAPI.Event = - this._onDidChangeKubeconfig.event; - constructor(private configurationRegistry: ConfigurationRegistry) { + private kubeConfigWatcher: containerDesktopAPI.FileSystemWatcher | undefined; + + private readonly _onDidUpdateKubeconfig = new Emitter(); + readonly onDidUpdateKubeconfig: containerDesktopAPI.Event = + this._onDidUpdateKubeconfig.event; + + constructor( + private configurationRegistry: ConfigurationRegistry, + private fileSystemMonitoring: FilesystemMonitoring, + ) { this.kubeConfig = new KubeConfig(); } @@ -74,6 +81,7 @@ export class KubernetesClient { const kubernetesConfiguration = this.configurationRegistry.getConfiguration('kubernetes'); const userKubeconfigPath = kubernetesConfiguration.get('Kubeconfig'); if (userKubeconfigPath) { + this.setupWatcher(userKubeconfigPath); // check if path exists if (existsSync(userKubeconfigPath)) { this.kubeconfigPath = userKubeconfigPath; @@ -87,11 +95,38 @@ export class KubernetesClient { this.configurationRegistry.onDidChangeConfiguration(e => { if (e.key === 'kubernetes.Kubeconfig') { const val = e.value as string; + this.setupWatcher(val); this.setKubeconfig(Uri.file(val)); } }); } + setupWatcher(kubeconfigFile: string): void { + // cancel the previous one (if any) + this.kubeConfigWatcher?.dispose(); + + // monitor the kube config file for changes + this.kubeConfigWatcher = this.fileSystemMonitoring.createFileSystemWatcher(kubeconfigFile); + + const location = Uri.file(kubeconfigFile); + + // needs to refresh + this.kubeConfigWatcher.onDidChange(() => { + this._onDidUpdateKubeconfig.fire({ type: 'UPDATE', location }); + this.refresh(); + }); + + this.kubeConfigWatcher.onDidCreate(() => { + this._onDidUpdateKubeconfig.fire({ type: 'CREATE', location }); + this.refresh(); + }); + + this.kubeConfigWatcher.onDidDelete(() => { + this._onDidUpdateKubeconfig.fire({ type: 'DELETE', location }); + this.kubeConfig = new KubeConfig(); + }); + } + getContexts(): Context[] { return this.kubeConfig.contexts; } @@ -242,11 +277,14 @@ export class KubernetesClient { return Uri.file(this.kubeconfigPath); } - async setKubeconfig(newLocation: containerDesktopAPI.Uri): Promise { - const oldLocation = Uri.file(this.kubeconfigPath); - this.kubeconfigPath = newLocation.fsPath; + async setKubeconfig(location: containerDesktopAPI.Uri): Promise { + this.kubeconfigPath = location.fsPath; this.refresh(); // notify change - this._onDidChangeKubeconfig.fire({ oldLocation, newLocation }); + this._onDidUpdateKubeconfig.fire({ type: 'UPDATE', location }); + } + + async stop(): Promise { + this.kubeConfigWatcher?.dispose(); } }