diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index a8cafcaeda..88da45d3fe 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -12,25 +12,21 @@ import { PassThrough } from "stream"; import type { ApiManager } from "../api-manager"; import { apiManager } from "../api-manager"; import type { FetchMock } from "jest-fetch-mock/types"; +import { DeploymentApi, Ingress, IngressApi, Pod, PodApi } from "../endpoints"; +import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; jest.mock("../api-manager"); const mockApiManager = apiManager as jest.Mocked; const mockFetch = fetch as FetchMock; -class TestKubeObject extends KubeObject { - static kind = "Pod"; - static namespaced = true; - static apiBase = "/api/v1/pods"; -} - -class TestKubeApi extends KubeApi { - public async checkPreferredVersion() { - return super.checkPreferredVersion(); - } -} - describe("forRemoteCluster", () => { + beforeEach(async () => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + await di.runSetups(); + }); + it("builds api client for KubeObject", async () => { const api = forRemoteCluster({ cluster: { @@ -39,7 +35,7 @@ describe("forRemoteCluster", () => { user: { token: "daa", }, - }, TestKubeObject); + }, Pod); expect(api).toBeInstanceOf(KubeApi); }); @@ -52,9 +48,9 @@ describe("forRemoteCluster", () => { user: { token: "daa", }, - }, TestKubeObject, TestKubeApi); + }, Pod, PodApi); - expect(api).toBeInstanceOf(TestKubeApi); + expect(api).toBeInstanceOf(PodApi); }); it("calls right api endpoint", async () => { @@ -65,7 +61,7 @@ describe("forRemoteCluster", () => { user: { token: "daa", }, - }, TestKubeObject); + }, Pod); mockFetch.mockResponse(async (request: any) => { expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods"); @@ -84,7 +80,11 @@ describe("forRemoteCluster", () => { describe("KubeApi", () => { let request: KubeJsonApi; - beforeEach(() => { + beforeEach(async () => { + const di = getDiForUnitTesting({ doGeneralOverrides: true }); + + await di.runSetups(); + request = new KubeJsonApi({ serverAddress: `http://127.0.0.1:9999`, apiBase: "/api-kube", @@ -121,9 +121,9 @@ describe("KubeApi", () => { const apiBase = "/apis/networking.k8s.io/v1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; - const kubeApi = new KubeApi({ + const kubeApi = new IngressApi({ request, - objectConstructor: KubeObject, + objectConstructor: Ingress, apiBase, fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, @@ -164,9 +164,10 @@ describe("KubeApi", () => { const apiBase = "apis/networking.k8s.io/v1/ingresses"; const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; - const kubeApi = new KubeApi({ + const kubeApi = new IngressApi({ request, objectConstructor: Object.assign(KubeObject, { apiBase }), + kind: "Ingress", fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, }); @@ -183,41 +184,38 @@ describe("KubeApi", () => { it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => { expect.hasAssertions(); - const api = new TestKubeApi({ - objectConstructor: TestKubeObject, + const api = new IngressApi({ + objectConstructor: Ingress, checkPreferredVersion: true, fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"], request: { get: jest.fn() - .mockImplementationOnce((path: string) => { - expect(path).toBe("/apis/networking.k8s.io/v1"); - - throw new Error("no"); - }) - .mockImplementationOnce((path: string) => { - expect(path).toBe("/apis/extensions/v1beta1"); - - return { - resources: [ - { - name: "ingresses", - }, - ], - }; - }) - .mockImplementationOnce((path: string) => { - expect(path).toBe("/apis/extensions"); - - return { - preferredVersion: { - version: "v1beta1", - }, - }; + .mockImplementation((path: string) => { + switch (path) { + case "/apis/networking.k8s.io/v1": + throw new Error("no"); + case "/apis/extensions/v1beta1": + return { + resources: [ + { + name: "ingresses", + }, + ], + }; + case "/apis/extensions": + return { + preferredVersion: { + version: "v1beta1", + }, + }; + default: + throw new Error("unknown path"); + } }), } as Partial as KubeJsonApi, }); - await api.checkPreferredVersion(); + await (api as any).checkPreferredVersion(); expect(api.apiVersionPreferred).toBe("v1beta1"); expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything()); @@ -226,41 +224,38 @@ describe("KubeApi", () => { it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => { expect.hasAssertions(); - const api = new TestKubeApi({ - objectConstructor: TestKubeObject, + const api = new PodApi({ + objectConstructor: Pod, checkPreferredVersion: true, fallbackApiBases: ["/api/v1beta1/pods"], request: { get: jest.fn() - .mockImplementationOnce((path: string) => { - expect(path).toBe("/api/v1"); - - throw new Error("no"); - }) - .mockImplementationOnce((path: string) => { - expect(path).toBe("/api/v1beta1"); - - return { - resources: [ - { - name: "pods", - }, - ], - }; - }) - .mockImplementationOnce((path: string) => { - expect(path).toBe("/api"); - - return { - preferredVersion: { - version: "v1beta1", - }, - }; + .mockImplementation((path: string) => { + switch (path) { + case "/api/v1": + throw new Error("no"); + case "/api/v1beta1": + return { + resources: [ + { + name: "pods", + }, + ], + }; + case "/api": + return { + preferredVersion: { + version: "v1beta1", + }, + }; + default: + throw new Error("unknown path"); + } }), } as Partial as KubeJsonApi, }); - await api.checkPreferredVersion(); + await (api as any).checkPreferredVersion(); expect(api.apiVersionPreferred).toBe("v1beta1"); expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything()); @@ -268,12 +263,11 @@ describe("KubeApi", () => { }); describe("patch", () => { - let api: TestKubeApi; + let api: DeploymentApi; beforeEach(() => { - api = new TestKubeApi({ + api = new DeploymentApi({ request, - objectConstructor: TestKubeObject, }); }); @@ -345,12 +339,12 @@ describe("KubeApi", () => { }); describe("delete", () => { - let api: TestKubeApi; + let api: PodApi; beforeEach(() => { - api = new TestKubeApi({ + api = new PodApi({ request, - objectConstructor: TestKubeObject, + objectConstructor: Pod, }); }); @@ -404,13 +398,13 @@ describe("KubeApi", () => { }); describe("watch", () => { - let api: TestKubeApi; + let api: PodApi; let stream: PassThrough; beforeEach(() => { - api = new TestKubeApi({ + api = new PodApi({ request, - objectConstructor: TestKubeObject, + objectConstructor: Pod, }); stream = new PassThrough(); }); @@ -581,12 +575,12 @@ describe("KubeApi", () => { }); describe("create", () => { - let api: TestKubeApi; + let api: PodApi; beforeEach(() => { - api = new TestKubeApi({ + api = new PodApi({ request, - objectConstructor: TestKubeObject, + objectConstructor: Pod, }); }); @@ -678,12 +672,12 @@ describe("KubeApi", () => { }); describe("update", () => { - let api: TestKubeApi; + let api: PodApi; beforeEach(() => { - api = new TestKubeApi({ + api = new PodApi({ request, - objectConstructor: TestKubeObject, + objectConstructor: Pod, }); }); diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 75add27797..cd92ef9986 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -8,7 +8,6 @@ import { isFunction, merge } from "lodash"; import { stringify } from "querystring"; import { apiKubePrefix, isDevelopment } from "../../common/vars"; -import logger from "../../main/logger"; import { apiManager } from "./api-manager"; import { apiBase, apiKube } from "./index"; import { createKubeApiURL, parseKubeApi } from "./kube-api-parse"; @@ -27,6 +26,9 @@ import { Agent } from "https"; import type { Patch } from "rfc6902"; import assert from "assert"; import type { PartialDeep } from "type-fest"; +import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import loggerInjectable from "../logger.injectable"; +import type { Logger } from "../logger"; /** * The options used for creating a `KubeApi` @@ -35,7 +37,7 @@ export interface IKubeApiOptions /** * base api-path for listing all resources, e.g. "/api/v1/pods" * - * If not specified then will be the one on the `objectConstructor` + * Must be provided either here or under `objectConstructor.apiBase` * @deprecated should be specified by `objectConstructor` */ apiBase?: string; @@ -46,11 +48,13 @@ export interface IKubeApiOptions objectConstructor: KubeObjectConstructor; /** + * Must be provided either here or under `objectConstructor.namespaced` * @deprecated should be specified by `objectConstructor` */ isNamespaced?: boolean; /** + * Must be provided either here or under `objectConstructor.kind` * @deprecated should be specified by `objectConstructor` */ kind?: string; @@ -317,6 +321,7 @@ export class KubeApi< protected readonly doCheckPreferredVersion: boolean; protected readonly fullApiPathname: string; protected readonly fallbackApiBases?: string[]; + protected readonly logger: Logger; constructor({ objectConstructor, @@ -331,10 +336,12 @@ export class KubeApi< assert(request, "request MUST be provided if not in a cluster page frame context"); const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(fullApiPathname); + const di = getLegacyGlobalDiForExtensionApi(); assert(kind); assert(apiPrefix); + this.logger = di.inject(loggerInjectable); this.doCheckPreferredVersion = doCheckPreferredVersion; this.fallbackApiBases = fallbackApiBases; this.fullApiPathname = fullApiPathname; @@ -353,7 +360,7 @@ export class KubeApi< get apiVersionWithGroup() { return [this.apiGroup, this.apiVersionPreferred ?? this.apiVersion] - .filter(isDefined) + .filter(Boolean) .join("/"); } @@ -375,10 +382,10 @@ export class KubeApi< const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl); // Request available resources - const response = await this.request.get(`${apiPrefix}/${apiVersionWithGroup}`); + const { resources } = await this.request.get(`${apiPrefix}/${apiVersionWithGroup}`) as IKubeResourceList; // If the resource is found in the group, use this apiUrl - if (response.resources?.find(kubeResource => kubeResource.name === resource)) { + if (resources.find(({ name }) => name === resource)) { return { apiPrefix, apiGroup }; } } catch (error) { @@ -398,7 +405,7 @@ export class KubeApi< return await this.getLatestApiPrefixGroup(); } catch (error) { // If valid API wasn't found, log the error and return defaults below - logger.error(`[KUBE-API]: ${error}`); + this.logger.error(`[KUBE-API]: ${error}`); } } @@ -422,8 +429,8 @@ export class KubeApi< this.apiPrefix = apiPrefix; this.apiGroup = apiGroup; - const url = [apiPrefix, apiGroup].filter(isDefined).join("/"); - const res = await this.request.get(url); + const url = [apiPrefix, apiGroup].filter(Boolean).join("/"); + const res = await this.request.get(url) as IKubePreferredVersion; this.apiVersionPreferred = res?.preferredVersion?.version; @@ -663,7 +670,7 @@ export class KubeApi< const abortController = new WrappedAbortController(opts.abortController); abortController.signal.addEventListener("abort", () => { - logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`); + this.logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`); clearTimeout(timedRetry); }); @@ -674,7 +681,7 @@ export class KubeApi< timeout: 600_000, }); - logger.info(`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`); + this.logger.info(`[KUBE-API] watch (${watchId}) ${retry === true ? "retried" : "started"} ${watchUrl}`); responsePromise .then(response => { @@ -682,7 +689,7 @@ export class KubeApi< let requestRetried = false; if (!response.ok) { - logger.warn(`[KUBE-API] watch (${watchId}) error response ${watchUrl}`, { status: response.status }); + this.logger.warn(`[KUBE-API] watch (${watchId}) error response ${watchUrl}`, { status: response.status }); return callback(null, response); } @@ -699,7 +706,7 @@ export class KubeApi< // Close current request abortController.abort(); - logger.info(`[KUBE-API] Watch timeout set, but not retried, retrying now`); + this.logger.info(`[KUBE-API] Watch timeout set, but not retried, retrying now`); requestRetried = true; @@ -718,7 +725,7 @@ export class KubeApi< return; } - logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`); + this.logger.info(`[KUBE-API] watch (${watchId}) ${eventName} ${watchUrl}`); requestRetried = true; @@ -747,7 +754,7 @@ export class KubeApi< }); }) .catch(error => { - logger.error(`[KUBE-API] watch (${watchId}) throwed ${watchUrl}`, error); + this.logger.error(`[KUBE-API] watch (${watchId}) throwed ${watchUrl}`, error); callback(null, error); });