feat: Reload kubernetes context if it is changed over time (#813)

Before it was read only at startup.
Also, move the watcher in the core part and expose it with API

Change-Id: I574d9dbfcacdfcdcaea8e89230fe24cb01920fb0
Signed-off-by: Florent Benoit <fbenoit@redhat.com>

Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
Florent BENOIT 2022-11-14 21:12:36 +01:00 committed by GitHub
parent fa540d320e
commit df9172a921
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 60 deletions

View file

@ -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<void> {
// 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) => {

View file

@ -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<KubeConfigChangeEvent>;
export const onDidUpdateKubeconfig: Event<KubeconfigUpdateEvent>;
export function setKubeconfig(kubeconfig: Uri): Promise<void>;
}
/**
* 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;
}
}

View file

@ -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<string, ActivatedExtension>();
private analyzedExtensions = new Map<string, AnalyzedExtension>();
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<ExtensionInfo[]> {
return Array.from(this.analyzedExtensions.values()).map(extension => ({
@ -393,8 +391,8 @@ export class ExtensionLoader {
async setKubeconfig(kubeconfig: containerDesktopAPI.Uri): Promise<void> {
return kubernetesClient.setKubeconfig(kubeconfig);
},
onDidChangeKubeconfig: (listener, thisArg, disposables) => {
return kubernetesClient.onDidChangeKubeconfig(listener, thisArg, disposables);
onDidUpdateKubeconfig: (listener, thisArg, disposables) => {
return kubernetesClient.onDidUpdateKubeconfig(listener, thisArg, disposables);
},
};

View file

@ -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);

View file

@ -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<containerDesktopAPI.KubeConfigChangeEvent>();
readonly onDidChangeKubeconfig: containerDesktopAPI.Event<containerDesktopAPI.KubeConfigChangeEvent> =
this._onDidChangeKubeconfig.event;
constructor(private configurationRegistry: ConfigurationRegistry) {
private kubeConfigWatcher: containerDesktopAPI.FileSystemWatcher | undefined;
private readonly _onDidUpdateKubeconfig = new Emitter<containerDesktopAPI.KubeconfigUpdateEvent>();
readonly onDidUpdateKubeconfig: containerDesktopAPI.Event<containerDesktopAPI.KubeconfigUpdateEvent> =
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<string>('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<void> {
const oldLocation = Uri.file(this.kubeconfigPath);
this.kubeconfigPath = newLocation.fsPath;
async setKubeconfig(location: containerDesktopAPI.Uri): Promise<void> {
this.kubeconfigPath = location.fsPath;
this.refresh();
// notify change
this._onDidChangeKubeconfig.fire({ oldLocation, newLocation });
this._onDidUpdateKubeconfig.fire({ type: 'UPDATE', location });
}
async stop(): Promise<void> {
this.kubeConfigWatcher?.dispose();
}
}