From 0a1f5f727b3b562b7894b5beca13126fb073a3a9 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Tue, 5 Nov 2024 16:43:14 +0100 Subject: [PATCH 1/2] fix: issue with remove checluster CRD Signed-off-by: Anatolii Bazko --- src/api/kube-client.ts | 131 +++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/src/api/kube-client.ts b/src/api/kube-client.ts index 8e571162e..895a19403 100644 --- a/src/api/kube-client.ts +++ b/src/api/kube-client.ts @@ -42,21 +42,21 @@ import { Watch, V1CustomResourceDefinition, V1ValidatingWebhookConfiguration, V1MutatingWebhookConfiguration, } from '@kubernetes/client-node' -import { Cluster } from '@kubernetes/client-node/dist/config_types' -import axios, { AxiosRequestConfig } from 'axios' -import { ux } from '@oclif/core' +import {Cluster} from '@kubernetes/client-node/dist/config_types' +import axios, {AxiosRequestConfig} from 'axios' +import {ux} from '@oclif/core' import * as execa from 'execa' import * as fs from 'node:fs' import * as https from 'node:https' import * as net from 'node:net' -import { Writable } from 'node:stream' +import {Writable} from 'node:stream' import { newError, sleep, } from '../utils/utls' import {CheCtlContext, KubeHelperContext} from '../context' -import { V1Certificate } from './types/cert-manager' -import { CatalogSource, ClusterServiceVersion, ClusterServiceVersionList, InstallPlan, Subscription } from './types/olm' +import {V1Certificate} from './types/cert-manager' +import {CatalogSource, ClusterServiceVersion, ClusterServiceVersionList, InstallPlan, Subscription} from './types/olm' import {EclipseChe} from '../tasks/installers/eclipse-che/eclipse-che' import {CheCluster} from './types/che-cluster' @@ -112,7 +112,7 @@ export class KubeClient { rejectUnauthorized: false, requestCert: true, }), - headers: token && { Authorization: 'bearer ' + token }, + headers: token && {Authorization: 'bearer ' + token}, } const response = await axios.get(`${endpoint}`, config) @@ -182,7 +182,7 @@ export class KubeClient { async applyResource(yamlPath: string, opts = ''): Promise { const command = `kubectl apply -f ${yamlPath} ${opts}` - await execa(command, { timeout: 60_000, shell: true }) + await execa(command, {timeout: 60_000, shell: true}) } async createNamespace(namespace: V1Namespace): Promise { @@ -357,7 +357,7 @@ export class KubeClient { async getPodListByLabel(namespace: string, labelSelector: string): Promise { const k8sCoreApi = this.kubeConfig.makeApiClient(CoreV1Api) try { - const { body: podList } = await k8sCoreApi.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, labelSelector) + const {body: podList} = await k8sCoreApi.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, labelSelector) return podList.items } catch (e: any) { throw this.wrapK8sClientError(e) @@ -558,7 +558,7 @@ export class KubeClient { async getConfigMap(name: string, namespace: string): Promise { const k8sCoreApi = this.kubeConfig.makeApiClient(CoreV1Api) try { - const { body } = await k8sCoreApi.readNamespacedConfigMap(name, namespace) + const {body} = await k8sCoreApi.readNamespacedConfigMap(name, namespace) return body } catch (e: any) { if (e.response.statusCode !== 404) { @@ -570,7 +570,7 @@ export class KubeClient { async listConfigMaps(namespace: string, labelSelector?: string): Promise { const k8sCoreApi = this.kubeConfig.makeApiClient(CoreV1Api) try { - const { body } = await k8sCoreApi.listNamespacedConfigMap(namespace, undefined, undefined, undefined, undefined, labelSelector) + const {body} = await k8sCoreApi.listNamespacedConfigMap(namespace, undefined, undefined, undefined, undefined, labelSelector) return body.items } catch (e: any) { throw this.wrapK8sClientError(e) @@ -582,7 +582,7 @@ export class KubeClient { async getConfigMapValue(name: string, namespace: string, key: string): Promise { const k8sCoreApi = this.kubeConfig.makeApiClient(CoreV1Api) try { - const { body } = await k8sCoreApi.readNamespacedConfigMap(name, namespace) + const {body} = await k8sCoreApi.readNamespacedConfigMap(name, namespace) if (body.data) { return body.data[key] } @@ -626,9 +626,10 @@ export class KubeClient { async getNamespace(namespace: string): Promise { const k8sApi = this.kubeConfig.makeApiClient(CoreV1Api) try { - const { body } = await k8sApi.readNamespace(namespace) + const {body} = await k8sApi.readNamespace(namespace) return body - } catch {} + } catch { + } } async patchNamespacedCustomObject(name: string, namespace: string, patch: any, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { @@ -654,7 +655,7 @@ export class KubeClient { async getClusterCustomObject(group: string, version: string, plural: string, name: any): Promise { const k8sCoreApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await k8sCoreApi.getClusterCustomObject(group, version, plural, name) + const {body} = await k8sCoreApi.getClusterCustomObject(group, version, plural, name) return body } catch (e: any) { if (e.response.statusCode !== 404) { @@ -1041,9 +1042,11 @@ export class KubeClient { async replaceCustomResourceDefinition(crd: V1CustomResourceDefinition): Promise { const k8sApi = this.kubeConfig.makeApiClient(ApiextensionsV1Api) try { - const response = await k8sApi.readCustomResourceDefinition(crd.metadata!.name!) + if (!crd.metadata!.resourceVersion) { + const response = await k8sApi.readCustomResourceDefinition(crd.metadata!.name!) + crd.metadata!.resourceVersion = (response.body as any).metadata.resourceVersion + } - crd.metadata!.resourceVersion = (response.body as any).metadata.resourceVersion await k8sApi.replaceCustomResourceDefinition(crd.metadata!.name!, crd) } catch (e: any) { throw this.wrapK8sClientError(e) @@ -1053,7 +1056,7 @@ export class KubeClient { async getCustomResourceDefinition(name: string): Promise { const k8sApi = this.kubeConfig.makeApiClient(ApiextensionsV1Api) try { - const { body } = await k8sApi.readCustomResourceDefinition(name) + const {body} = await k8sApi.readCustomResourceDefinition(name) return body } catch (e: any) { if (e.response && e.response.statusCode === 404) { @@ -1099,10 +1102,8 @@ export class KubeClient { // 1. Disable conversion webhook crd.spec.conversion = null - await this.replaceCustomResourceDefinition(crd) // 2. Patch CRD to unblock potential invalid resource error - crd = await this.getCustomResourceDefinition(crdName) for (let i = 0; i < crd.spec.versions.length; i++) { if (crd.spec.versions[i].schema?.openAPIV3Schema?.properties?.spec) { crd.spec.versions[i].schema.openAPIV3Schema.properties.spec = {type: 'object', properties: {}} @@ -1139,7 +1140,7 @@ export class KubeClient { const name = resource.metadata.name const namespace = resource.metadata.namespace try { - await this.patchNamespacedCustomObject(name, namespace, { metadata: { finalizers: null } }, apiGroup, version, plural) + await this.patchNamespacedCustomObject(name, namespace, {metadata: {finalizers: null}}, apiGroup, version, plural) } catch (error: any) { if (error.cause?.body?.reason === 'NotFound') { continue @@ -1190,7 +1191,7 @@ export class KubeClient { async listClusterCustomObject(resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.listClusterCustomObject(resourceAPIGroup, resourceAPIVersion, resourcePlural) + const {body} = await customObjectsApi.listClusterCustomObject(resourceAPIGroup, resourceAPIVersion, resourcePlural) return (body as any).items ? (body as any).items : [] } catch (e: any) { if (e.response && e.response.statusCode === 404) { @@ -1219,7 +1220,7 @@ export class KubeClient { async listCatalogSource(namespace: string, labelSelector: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', undefined, undefined, undefined, labelSelector) + const {body} = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', undefined, undefined, undefined, labelSelector) return (body as any).items as CatalogSource[] } catch (e: any) { throw this.wrapK8sClientError(e) @@ -1229,7 +1230,7 @@ export class KubeClient { async getCatalogSource(name: string, namespace: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', name) + const {body} = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', name) return body as CatalogSource } catch (e: any) { if (e.response && e.response.statusCode === 404) { @@ -1256,7 +1257,7 @@ export class KubeClient { return new Promise(async (resolve, reject) => { const watcher = new Watch(this.kubeConfig) const request = await watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/catalogsources`, - { fieldSelector: `metadata.name=${name}` }, + {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { request.response.destroy() resolve(obj as CatalogSource) @@ -1304,7 +1305,7 @@ export class KubeClient { async getOperatorSubscriptionByPackage(packageName: string, namespace: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions') + const {body} = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions') return ((body as any).items as Subscription[]).find(sub => sub.spec.name === packageName) } catch (e: any) { throw this.wrapK8sClientError(e) @@ -1314,7 +1315,7 @@ export class KubeClient { async getOperatorSubscription(name: string, namespace: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions', name) + const {body} = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions', name) return body as Subscription } catch (e: any) { if (e.response.statusCode !== 404) { @@ -1329,7 +1330,7 @@ export class KubeClient { return new Promise(async (resolve, reject) => { const watcher = new Watch(this.kubeConfig) const request = await watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/subscriptions`, - { fieldSelector: `metadata.name=${name}` }, + {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: unknown) => { const subscription = obj as Subscription if (subscription.status?.installedCSV) { @@ -1360,7 +1361,7 @@ export class KubeClient { return new Promise(async (resolve, reject) => { const watcher = new Watch(this.kubeConfig) const request = await watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/clusterserviceversions`, - { fieldSelector: `metadata.name=${name}` }, + {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { const csv = obj as ClusterServiceVersion if (csv.status?.phase) { @@ -1404,7 +1405,7 @@ export class KubeClient { return new Promise(async (resolve, reject) => { const watcher = new Watch(this.kubeConfig) const request = await watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/subscriptions`, - { fieldSelector: `metadata.name=${name}` }, + {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: unknown) => { const subscription = obj as Subscription if (subscription.status?.installplan) { @@ -1437,7 +1438,7 @@ export class KubeClient { approved: true, }, } - await customObjectsApi.patchNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'installplans', name, patch, undefined, undefined, undefined, { headers: { 'Content-Type': 'application/merge-patch+json' } }) + await customObjectsApi.patchNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'installplans', name, patch, undefined, undefined, undefined, {headers: {'Content-Type': 'application/merge-patch+json'}}) } catch (e: any) { throw this.wrapK8sClientError(e) } @@ -1449,7 +1450,7 @@ export class KubeClient { return new Promise(async (resolve, reject) => { const watcher = new Watch(this.kubeConfig) const request = await watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/installplans`, - { fieldSelector: `metadata.name=${name}` }, + {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { const installPlan = obj as InstallPlan if (installPlan.status?.phase === 'Failed') { @@ -1493,7 +1494,7 @@ export class KubeClient { async getCSV(name: string, namespace: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions', name) + const {body} = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions', name) return body as ClusterServiceVersion } catch (e: any) { if (e.response.statusCode !== 404) { @@ -1514,7 +1515,7 @@ export class KubeClient { async listCSV(namespace: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions') + const {body} = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions') return body as ClusterServiceVersionList } catch (e: any) { throw this.wrapK8sClientError(e) @@ -1765,14 +1766,14 @@ export class KubeClient { // Set up watcher const watcher = new Watch(this.kubeConfig) const request = await watcher - .watch(`/api/v1/namespaces/${namespace}/configmaps/`, { fieldSelector: `metadata.name=${name}` }, (_phase: string, _obj: any) => { - request.abort() - resolve() - }, error => { - if (error) { - reject(error) - } - }) + .watch(`/api/v1/namespaces/${namespace}/configmaps/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, _obj: any) => { + request.abort() + resolve() + }, error => { + if (error) { + reject(error) + } + }) // Automatically stop watching after timeout const timeoutHandler = setTimeout(() => { @@ -1798,31 +1799,31 @@ export class KubeClient { // Set up watcher const watcher = new Watch(this.kubeConfig) const request = await watcher - .watch(`/api/v1/namespaces/${namespace}/secrets/`, { fieldSelector: `metadata.name=${name}` }, (_phase: string, obj: any) => { - const secret = obj as V1Secret - - // Check all required data fields to be present - if (dataKeys.length > 0 && secret.data) { - for (const key of dataKeys) { - if (!secret.data[key]) { - // Key is missing or empty - return + .watch(`/api/v1/namespaces/${namespace}/secrets/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { + const secret = obj as V1Secret + + // Check all required data fields to be present + if (dataKeys.length > 0 && secret.data) { + for (const key of dataKeys) { + if (!secret.data[key]) { + // Key is missing or empty + return + } } } - } - // The secret with all specified fields is present, stop watching - if (request) { - request.abort() - } + // The secret with all specified fields is present, stop watching + if (request) { + request.abort() + } - // Release awaiter - resolve() - }, error => { - if (error) { - reject(error) - } - }) + // Release awaiter + resolve() + }, error => { + if (error) { + reject(error) + } + }) // Automatically stop watching after timeout const timeoutHandler = setTimeout(() => { @@ -1863,7 +1864,7 @@ export class KubeClient { const logHelper = new Log(this.kubeConfig) const stream = new Writable() stream._write = function (chunk, encoding, done) { - fs.appendFileSync(filename, chunk, { encoding }) + fs.appendFileSync(filename, chunk, {encoding}) done() } @@ -1874,7 +1875,7 @@ export class KubeClient { } else { resolve() } - }, { follow }) + }, {follow}) }) } From f9438b3ff1f58fc090551d75fa9ee6067526aa8c Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Tue, 5 Nov 2024 16:45:55 +0100 Subject: [PATCH 2/2] fix: typo Signed-off-by: Anatolii Bazko --- src/api/kube-client.ts | 63 +++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/api/kube-client.ts b/src/api/kube-client.ts index 895a19403..bbf66c990 100644 --- a/src/api/kube-client.ts +++ b/src/api/kube-client.ts @@ -628,8 +628,7 @@ export class KubeClient { try { const {body} = await k8sApi.readNamespace(namespace) return body - } catch { - } + } catch {} } async patchNamespacedCustomObject(name: string, namespace: string, patch: any, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { @@ -1095,7 +1094,7 @@ export class KubeClient { async deleteAllCustomResourcesAndCrd(crdName: string, apiGroup: string, version: string, plural: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) - let crd = await this.getCustomResourceDefinition(crdName) + const crd = await this.getCustomResourceDefinition(crdName) if (!crd) { return } @@ -1766,14 +1765,14 @@ export class KubeClient { // Set up watcher const watcher = new Watch(this.kubeConfig) const request = await watcher - .watch(`/api/v1/namespaces/${namespace}/configmaps/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, _obj: any) => { - request.abort() - resolve() - }, error => { - if (error) { - reject(error) - } - }) + .watch(`/api/v1/namespaces/${namespace}/configmaps/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, _obj: any) => { + request.abort() + resolve() + }, error => { + if (error) { + reject(error) + } + }) // Automatically stop watching after timeout const timeoutHandler = setTimeout(() => { @@ -1799,31 +1798,31 @@ export class KubeClient { // Set up watcher const watcher = new Watch(this.kubeConfig) const request = await watcher - .watch(`/api/v1/namespaces/${namespace}/secrets/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { - const secret = obj as V1Secret - - // Check all required data fields to be present - if (dataKeys.length > 0 && secret.data) { - for (const key of dataKeys) { - if (!secret.data[key]) { - // Key is missing or empty - return - } + .watch(`/api/v1/namespaces/${namespace}/secrets/`, {fieldSelector: `metadata.name=${name}`}, (_phase: string, obj: any) => { + const secret = obj as V1Secret + + // Check all required data fields to be present + if (dataKeys.length > 0 && secret.data) { + for (const key of dataKeys) { + if (!secret.data[key]) { + // Key is missing or empty + return } } + } - // The secret with all specified fields is present, stop watching - if (request) { - request.abort() - } + // The secret with all specified fields is present, stop watching + if (request) { + request.abort() + } - // Release awaiter - resolve() - }, error => { - if (error) { - reject(error) - } - }) + // Release awaiter + resolve() + }, error => { + if (error) { + reject(error) + } + }) // Automatically stop watching after timeout const timeoutHandler = setTimeout(() => {