From 41896a228857f70498acc0716ed37d4a5738cda0 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 11 Nov 2020 13:02:48 +0200 Subject: [PATCH] fix: allow to select kube-api objects with label & field selectors Signed-off-by: Roman --- src/renderer/api/endpoints/crd.api.ts | 7 ++-- src/renderer/api/kube-api-versioned.ts | 56 ------------------------- src/renderer/api/kube-api.ts | 58 ++++++++++++++++++++++---- 3 files changed, 55 insertions(+), 66 deletions(-) delete mode 100644 src/renderer/api/kube-api-versioned.ts diff --git a/src/renderer/api/endpoints/crd.api.ts b/src/renderer/api/endpoints/crd.api.ts index 7b561041f7..1916d71f1b 100644 --- a/src/renderer/api/endpoints/crd.api.ts +++ b/src/renderer/api/endpoints/crd.api.ts @@ -1,6 +1,6 @@ import { KubeObject } from "../kube-object"; -import { VersionedKubeApi } from "../kube-api-versioned"; import { crdResourcesURL } from "../../components/+custom-resources/crd.route"; +import { KubeApi } from "../kube-api"; type AdditionalPrinterColumnsCommon = { name: string; @@ -146,6 +146,7 @@ export class CustomResourceDefinition extends KubeObject { } } -export const crdApi = new VersionedKubeApi({ - objectConstructor: CustomResourceDefinition +export const crdApi = new KubeApi({ + objectConstructor: CustomResourceDefinition, + checkPreferredVersion: true, }); diff --git a/src/renderer/api/kube-api-versioned.ts b/src/renderer/api/kube-api-versioned.ts deleted file mode 100644 index f6aa94c089..0000000000 --- a/src/renderer/api/kube-api-versioned.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { stringify } from "querystring"; -import { KubeObject } from "./kube-object"; -import { createKubeApiURL } from "./kube-api-parse"; -import { KubeApi, IKubeApiQueryParams, IKubeApiOptions } from "./kube-api"; -import { apiManager } from "./api-manager"; - -export class VersionedKubeApi extends KubeApi { - private preferredVersion?: string; - - constructor(opts: IKubeApiOptions) { - super(opts); - - this.getPreferredVersion().then(() => { - if (this.apiBase != opts.apiBase) - apiManager.registerApi(this.apiBase, this); - }); - } - - // override this property to make read-write - apiBase: string - - async getPreferredVersion() { - if (this.preferredVersion) return; - - const apiGroupVersion = await this.request.get<{ preferredVersion?: { version: string; }; }>(`${this.apiPrefix}/${this.apiGroup}`); - - if (!apiGroupVersion?.preferredVersion) return; - - this.preferredVersion = apiGroupVersion.preferredVersion.version; - - // update apiBase - this.apiBase = this.getUrl(); - } - - async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise { - await this.getPreferredVersion(); - return await super.list({namespace}, query); - } - async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise { - await this.getPreferredVersion(); - return super.get({ name, namespace }, query); - } - - getUrl({ name = "", namespace = "" } = {}, query?: Partial) { - const { apiPrefix, apiGroup, apiVersion, apiResource, preferredVersion, isNamespaced } = this; - - const resourcePath = createKubeApiURL({ - apiPrefix: apiPrefix, - apiVersion: `${apiGroup}/${preferredVersion ?? apiVersion}`, - resource: apiResource, - namespace: isNamespaced ? namespace : undefined, - name: name, - }); - return resourcePath + (query ? `?` + stringify(query) : ""); - } -} diff --git a/src/renderer/api/kube-api.ts b/src/renderer/api/kube-api.ts index b572bd774c..a85251b76a 100644 --- a/src/renderer/api/kube-api.ts +++ b/src/renderer/api/kube-api.ts @@ -16,6 +16,7 @@ export interface IKubeApiOptions { request?: KubeJsonApi; isNamespaced?: boolean; kind?: string; + checkPreferredVersion?: boolean; } export interface IKubeApiQueryParams { @@ -24,6 +25,14 @@ export interface IKubeApiQueryParams { timeoutSeconds?: number; limit?: number; // doesn't work with ?watch continue?: string; // might be used with ?limit from second request + labelSelector?: string | string[]; // restrict list of objects by their labels, e.g. labelSelector: ["label=value"] + fieldSelector?: string | string[]; // restrict list of objects by their fields, e.g. fieldSelector: "field=name" +} + +export interface IKubeApiPreferredVersion { + preferredVersion?: { + version: string; + } } export interface IKubeApiCluster { @@ -58,7 +67,7 @@ export class KubeApi { readonly apiPrefix: string readonly apiGroup: string readonly apiVersion: string - readonly apiVersionWithGroup: string + readonly apiVersionPreferred?: string; readonly apiResource: string readonly isNamespaced: boolean @@ -84,15 +93,36 @@ export class KubeApi { this.apiPrefix = apiPrefix; this.apiGroup = apiGroup; this.apiVersion = apiVersion; - this.apiVersionWithGroup = apiVersionWithGroup; this.apiResource = resource; this.request = request; this.objectConstructor = objectConstructor; + this.checkPreferredVersion(); this.parseResponse = this.parseResponse.bind(this); apiManager.registerApi(apiBase, this); } + get apiVersionWithGroup() { + return [this.apiGroup, this.apiVersionPreferred ?? this.apiVersion] + .filter(Boolean) + .join("/") + } + + protected async checkPreferredVersion() { + if (!this.options.checkPreferredVersion || this.apiVersionPreferred === undefined) { + return; + } + const res = await this.request.get(`${this.apiPrefix}/${this.apiGroup}`); + Object.defineProperty(this, "apiVersionPreferred", { + value: res?.preferredVersion?.version ?? null, + }); + + if (this.apiVersionPreferred) { + Object.defineProperty(this, "apiBase", { value: this.getUrl() }) + apiManager.registerApi(this.apiBase, this); + } + } + setResourceVersion(namespace = "", newVersion: string) { this.resourceVersions.set(namespace, newVersion); } @@ -106,15 +136,24 @@ export class KubeApi { } getUrl({ name = "", namespace = "" } = {}, query?: Partial) { - const { apiPrefix, apiVersionWithGroup, apiResource } = this; const resourcePath = createKubeApiURL({ - apiPrefix: apiPrefix, - apiVersion: apiVersionWithGroup, - resource: apiResource, + apiPrefix: this.apiPrefix, + apiVersion: this.apiVersionWithGroup, + resource: this.apiResource, namespace: this.isNamespaced ? namespace : undefined, name: name, }); - return resourcePath + (query ? `?` + stringify(query) : ""); + return resourcePath + (query ? `?` + stringify(this.normalizeQuery(query)) : ""); + } + + protected normalizeQuery(query: Partial = {}) { + if (query.labelSelector) { + query.labelSelector = [query.labelSelector].flat().join(",") + } + if (query.fieldSelector) { + query.fieldSelector = [query.fieldSelector].flat().join(",") + } + return query; } protected parseResponse(data: KubeJsonApiData | KubeJsonApiData[] | KubeJsonApiDataList, namespace?: string): any { @@ -144,18 +183,21 @@ export class KubeApi { } async list({ namespace = "" } = {}, query?: IKubeApiQueryParams): Promise { + await this.checkPreferredVersion(); return this.request .get(this.getUrl({ namespace }), { query }) .then(data => this.parseResponse(data, namespace)); } async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise { + await this.checkPreferredVersion(); return this.request .get(this.getUrl({ namespace, name }), { query }) .then(this.parseResponse); } async create({ name = "", namespace = "default" } = {}, data?: Partial): Promise { + await this.checkPreferredVersion(); const apiUrl = this.getUrl({ namespace }); return this.request @@ -173,6 +215,7 @@ export class KubeApi { } async update({ name = "", namespace = "default" } = {}, data?: Partial): Promise { + await this.checkPreferredVersion(); const apiUrl = this.getUrl({ namespace, name }); return this.request .put(apiUrl, { data }) @@ -180,6 +223,7 @@ export class KubeApi { } async delete({ name = "", namespace = "default" }) { + await this.checkPreferredVersion(); const apiUrl = this.getUrl({ namespace, name }); return this.request.del(apiUrl) }