From ceebd8838c29c0f923991ee42b048930db73f838 Mon Sep 17 00:00:00 2001 From: Juozas Gaigalas Date: Thu, 29 Jun 2023 17:30:00 +0300 Subject: [PATCH] 'gitrepo informer wip' --- src/extension.ts | 4 +- src/flux/fluxInformer.ts | 194 ++++++++++++++++++ src/types/kubernetes/kubernetesTypes.ts | 1 + .../treeviews/dataProviders/dataProvider.ts | 23 ++- 4 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/flux/fluxInformer.ts diff --git a/src/extension.ts b/src/extension.ts index be8a4428..8c204fd0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,7 +12,8 @@ import { CommandId, ContextId, GitOpsExtensionConstants } from './types/extensio import { TelemetryEvent } from './types/telemetryEventNames'; import { promptToInstallFlux } from './ui/promptToInstallFlux'; import { statusBar } from './ui/statusBar'; -import { clusterTreeViewProvider, createTreeViews, sourceTreeViewProvider, workloadTreeViewProvider } from './ui/treeviews/treeViews'; +import { clusterTreeViewProvider, createTreeViews, sourceTreeViewProvider, templateTreeViewProvider, workloadTreeViewProvider } from './ui/treeviews/treeViews'; +import { startFluxInformers } from 'flux/fluxInformer'; /** Disable interactive modal dialogs, useful for testing */ export let disableConfirmations = false; @@ -44,6 +45,7 @@ export async function activate(context: ExtensionContext) { // create gitops tree views createTreeViews(); + startFluxInformers(sourceTreeViewProvider, workloadTreeViewProvider, templateTreeViewProvider); // register gitops commands registerCommands(context); diff --git a/src/flux/fluxInformer.ts b/src/flux/fluxInformer.ts new file mode 100644 index 00000000..562d8d10 --- /dev/null +++ b/src/flux/fluxInformer.ts @@ -0,0 +1,194 @@ +// https://code.visualstudio.com/api/references/vscode-api#FileSystemWatcher +// make sure to test and debug switching kubeconfig from outside and from right click menu +// kubectl auth can-i watch gitrepository +// kubectl auth can-i watch kustomizations --all-namespaces +// kubectl api-resources -o wide +// experiment how the fallbacks work by default if watch is disabled + +// flux v 0.42: /apis/source.toolkit.fluxcd.io/v1beta2/gitrepositories +// flux prerelease: /apis/source.toolkit.fluxcd.io/v1/gitrepositories + +/* + * 1. exec kubectl proxy -p 0 + * 2. parse result to get port + * 3. watch proxy for aliveness. + * 4. watch kubeconfig for changes and update proxy process + * 5. recreate informer as needed + */ +import * as k8s from '@kubernetes/client-node'; +import { KubernetesListObject, KubernetesObject } from 'types/kubernetes/kubernetesTypes'; + +// export const fluxInformers: Record = {}; + +function startKubeProxy(): number | false { + return 57375; +} + +// interface KubernetesCustomObject { +// 'apiVersion'?: string; +// 'kind'?: string; +// 'metadata'?: k8s.V1ObjectMeta; +// 'spec'?: k8s.V1PodSpec; +// 'status'?: k8s.V1PodStatus; +// } + +// export class FluxInformer { +// public list(): KubernetesObject[] { +// return []; +// } +// } + +function createKubeProxyConfig(port: number): k8s.KubeConfig { + const kcDefault = new k8s.KubeConfig(); + kcDefault.loadFromDefault(); + + const cluster = { + name: kcDefault.getCurrentCluster()?.name, + server: `http://127.0.0.1:${port}`, + }; + + const user = kcDefault.getCurrentUser(); + + const context = { + name: kcDefault.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; +} + +function getAPIPaths() { + return { + 'gitrepositories': ['source.toolkit.fluxcd.io', 'v1'], + }; +} + + +// will start a self-healing informer for each resource type and namespaces +export async function startFluxInformers( + sourceDataProvider: any, + workloadDataProvider: any, + templateDataProvider: any): Promise { + const port = startKubeProxy(); + if(!port) { + return false; + } + + const kc = createKubeProxyConfig(port); + + const k8sCoreApi = kc.makeApiClient(k8s.CoreV1Api); + const k8sCustomApi = kc.makeApiClient(k8s.CustomObjectsApi); + + const plural = 'gitrepositories'; + const [group, version] = getAPIPaths()[plural]; + + const listFn = async () => { + const result = await k8sCustomApi.listClusterCustomObject(group, version, plural); + const kbody = result.body as KubernetesListObject; + return Promise.resolve({response: result.response, body: kbody}); + }; + + // const listFn = () => { + // const x = k8sCoreApi.listNamespacedPod('default'); + // return x; + // }; + + // listFnBad(); + // const a: ListPromise; + + // const inf2 = k8s.make + + const informer = k8s.makeInformer( + kc, + `/apis/${group}/${version}/${plural}`, + listFn, + ); + + + informer.on('add', (obj: KubernetesObject) => { + console.log(`Added: ${obj.metadata?.name}`); + sourceDataProvider.add(obj); + }); + informer.on('update', (obj: KubernetesObject) => { + console.log(`Updated: ${obj.metadata?.name}`); + sourceDataProvider.update(obj); + }); + informer.on('delete', (obj: KubernetesObject) => { + console.log(`Deleted: ${obj.metadata?.name}`); + sourceDataProvider.delete(obj); + }); + informer.on('error', (err: any) => { + console.error('ERRORed:', err); + // Restart informer after 5sec + setTimeout(() => { + console.log('Restarting informer...'); + informer.start(); + }, 2000); + }); + try { + await informer.start(); + } catch (err) { + console.error('flux resources informer unavailable:', err); + informer.stop(); + setTimeout(() => { + console.info('Restarting informer...'); + informer.start(); + // this needs to be recursive + }, 2000); + + return false; + } + const l = informer.list(); + console.log('list:', l); + + return true; +} + + +// const fn = k8sApi.depl + +// k8sCoreApi.listPodForAllNamespaces(); + + + +// const watch = new k8s.Watch(kc); +// // watch.watch('/api/v1/namespaces', {}, +// // watch.watch('/apis/apps/v1/deployments', {}, +// watch.watch('/apis/source.toolkit.fluxcd.io/v1beta2/gitrepositories', {}, +// (type, apiObj, watchObj) => { +// // console.log(type, apiObj, watchObj); +// if (type === 'ADDED') { +// // tslint:disable-next-line:no-console +// console.log(`NEW: ${apiObj.metadata.name}`); +// } else if (type === 'DELETED') { +// // tslint:disable-next-line:no-console +// console.log(`DELETED: ${apiObj.metadata.name}`); +// } +// }, +// err => { + // console.log(err); +// }, +// ); + + + +// const crApi = kc.makeApiClient(k8s.CustomObjectsApi); +// crApi.listClusterCustomObject('source.toolkit.fluxcd.io', 'v1', +// 'gitrepositories') +// .then(res => { +// console.log('GITREPO', res.body); +// }).catch(err => { +// console.log(err); +// }); + + + diff --git a/src/types/kubernetes/kubernetesTypes.ts b/src/types/kubernetes/kubernetesTypes.ts index 0ac05fd8..71d9f57b 100644 --- a/src/types/kubernetes/kubernetesTypes.ts +++ b/src/types/kubernetes/kubernetesTypes.ts @@ -1,6 +1,7 @@ import * as k8s from '@kubernetes/client-node'; export type KubernetesObject = k8s.KubernetesObject; +export type KubernetesListObject = k8s.KubernetesListObject; export type Condition = k8s.V1Condition; // Specify types from `@kubernetes/client-node` diff --git a/src/ui/treeviews/dataProviders/dataProvider.ts b/src/ui/treeviews/dataProviders/dataProvider.ts index 898c61a6..28c6c72a 100644 --- a/src/ui/treeviews/dataProviders/dataProvider.ts +++ b/src/ui/treeviews/dataProviders/dataProvider.ts @@ -1,5 +1,5 @@ import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode'; -import { Namespace } from 'types/kubernetes/kubernetesTypes'; +import { KubernetesObject, Namespace } from 'types/kubernetes/kubernetesTypes'; import { NamespaceNode } from '../nodes/namespaceNode'; import { TreeNode } from '../nodes/treeNode'; @@ -74,6 +74,27 @@ export class DataProvider implements TreeDataProvider { return Promise.resolve([]); } + public add(object: KubernetesObject) { + // find or create namespace TreeItem for node + // add update TreeItem + // this.refresh(treeItem) + console.log('add', object); + } + + public update(object: KubernetesObject) { + // find or create namespace TreeItem for node + // add update TreeItem + // this.refresh(treeItem) + console.log('add', object); + } + + public delete(object: KubernetesObject) { + // find or create namespace TreeItem for node + // add update TreeItem + // this.refresh(treeItem) + console.log('add', object); + } + groupByNamespace(namespaces: Namespace[], nodes: TreeNode[]): NamespaceNode[] { const namespaceNodes: NamespaceNode[] = [];