diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index 08cc9f2642..15b40185d2 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -131,7 +131,10 @@ describe("KubeApi", () => { checkPreferredVersion: true, }); - await kubeApi.get(); + await kubeApi.get({ + name: "foo", + namespace: "default", + }); expect(kubeApi.apiPrefix).toEqual("/apis"); expect(kubeApi.apiGroup).toEqual("networking.k8s.io"); }); @@ -171,7 +174,10 @@ describe("KubeApi", () => { checkPreferredVersion: true, }); - await kubeApi.get(); + await kubeApi.get({ + name: "foo", + namespace: "default", + }); expect(kubeApi.apiPrefix).toEqual("/apis"); expect(kubeApi.apiGroup).toEqual("extensions"); }); @@ -310,7 +316,7 @@ describe("KubeApi", () => { (fetch as any).mockResponse(async () => { return {}; }); - + api.watch({ namespace: "kube-system" }); expect(spy).toHaveBeenCalledWith("/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", expect.anything(), expect.anything()); }); @@ -321,7 +327,7 @@ describe("KubeApi", () => { (fetch as any).mockResponse(async () => { return {}; }); - + api.watch({ namespace: "kube-system", timeout: 60 }); expect(spy).toHaveBeenCalledWith("/api/v1/namespaces/kube-system/pods?watch=1&resourceVersion=", { query: { timeoutSeconds: 60 }}, expect.anything()); }); diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index e4ec4a95e9..8ac2919733 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -21,7 +21,6 @@ // Base class for building all kubernetes apis -import merge from "lodash/merge"; import { isFunction } from "lodash"; import { stringify } from "querystring"; import { apiKubePrefix, isDevelopment } from "../../common/vars"; @@ -219,6 +218,29 @@ const patchTypeHeaders: Record = { "strategic": "application/strategic-merge-patch+json", }; +export interface ResourceDescriptor { + /** + * The name of the kubernetes resource + */ + name: string; + + /** + * The namespace that the resource lives in (if the resource is namespaced) + * + * Note: if not provided and the resource kind is namespaced, then this defaults to `"default"` + */ + namespace?: string; +} + +export interface DeleteResourceDescriptor extends ResourceDescriptor { + /** + * This determinines how child resources should be handled by kubernetes + * + * @default "Background" + */ + propagationPolicy?: PropagationPolicy; +} + export class KubeApi { readonly kind: string; readonly apiBase: string; @@ -353,12 +375,12 @@ export class KubeApi { return this.list(params, { limit: 1 }); } - getUrl({ name = "", namespace = "" } = {}, query?: Partial) { + getUrl({ name, namespace = "default" }: Partial = {}, query?: Partial) { const resourcePath = createKubeApiURL({ apiPrefix: this.apiPrefix, apiVersion: this.apiVersionWithGroup, resource: this.apiResource, - namespace: this.isNamespaced ? namespace : undefined, + namespace: this.isNamespaced ? namespace ?? "default" : undefined, name, }); @@ -436,10 +458,10 @@ export class KubeApi { throw new Error(`GET multiple request to ${url} returned not an array: ${JSON.stringify(parsed)}`); } - async get({ name = "", namespace = "default" } = {}, query?: IKubeApiQueryParams): Promise { + async get(desc: ResourceDescriptor, query?: IKubeApiQueryParams): Promise { await this.checkPreferredVersion(); - const url = this.getUrl({ namespace, name }); + const url = this.getUrl(desc); const res = await this.request.get(url, { query }); const parsed = this.parseResponse(res); @@ -450,19 +472,18 @@ export class KubeApi { return parsed; } - async create({ name = "", namespace = "default" } = {}, data?: Partial): Promise { + async create({ name, namespace }: Partial, data?: Partial): Promise { await this.checkPreferredVersion(); const apiUrl = this.getUrl({ namespace }); const res = await this.request.post(apiUrl, { - data: merge({ - kind: this.kind, - apiVersion: this.apiVersionWithGroup, + data: { + ...data, metadata: { name, namespace, }, - }, data), + }, }); const parsed = this.parseResponse(res); @@ -473,11 +494,19 @@ export class KubeApi { return parsed; } - async update({ name = "", namespace = "default" } = {}, data?: Partial): Promise { + async update({ name, namespace }: ResourceDescriptor, data: Partial): Promise { await this.checkPreferredVersion(); const apiUrl = this.getUrl({ namespace, name }); - const res = await this.request.put(apiUrl, { data }); + const res = await this.request.put(apiUrl, { + data: { + ...data, + metadata: { + name, + namespace, + }, + }, + }); const parsed = this.parseResponse(res); if (Array.isArray(parsed)) { @@ -487,9 +516,9 @@ export class KubeApi { return parsed; } - async patch({ name = "", namespace = "default" } = {}, data?: Partial | Patch, strategy: KubeApiPatchType = "strategic"): Promise { + async patch(desc: ResourceDescriptor, data?: Partial | Patch, strategy: KubeApiPatchType = "strategic"): Promise { await this.checkPreferredVersion(); - const apiUrl = this.getUrl({ namespace, name }); + const apiUrl = this.getUrl(desc); const res = await this.request.patch(apiUrl, { data }, { headers: { @@ -505,16 +534,15 @@ export class KubeApi { return parsed; } - async delete({ name = "", namespace = "default", propagationPolicy = "Background" }: { name: string, namespace?: string, propagationPolicy?: PropagationPolicy }) { + async delete({ propagationPolicy = "Background", ...desc }: DeleteResourceDescriptor) { await this.checkPreferredVersion(); - const apiUrl = this.getUrl({ namespace, name }); - const reqInit = { + const apiUrl = this.getUrl(desc); + + return this.request.del(apiUrl, { query: { propagationPolicy, }, - }; - - return this.request.del(apiUrl, reqInit); + }); } getWatchUrl(namespace = "", query: IKubeApiQueryParams = {}) { diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 16f9c6457f..532ca175d8 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -369,7 +369,8 @@ export abstract class KubeObjectStore extends ItemStore return this.postUpdate( await this.api.update( { - name: item.getName(), namespace: item.getNs(), + name: item.getName(), + namespace: item.getNs(), }, data, ),