From fd84faf6cee1e673be1276c106431ee88a3abfd6 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 30 Nov 2021 11:27:20 -0500 Subject: [PATCH] Fix getLatestApiPrefixGroup to not fail when options.apiBase is not provided (#4462) --- src/common/k8s-api/__tests__/kube-api.test.ts | 87 +++++++++++-------- src/common/k8s-api/kube-api.ts | 14 +-- src/common/k8s-api/kube-object.store.ts | 2 +- 3 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/common/k8s-api/__tests__/kube-api.test.ts b/src/common/k8s-api/__tests__/kube-api.test.ts index b50403174e..33c2f9dc06 100644 --- a/src/common/k8s-api/__tests__/kube-api.test.ts +++ b/src/common/k8s-api/__tests__/kube-api.test.ts @@ -25,6 +25,7 @@ import { KubeJsonApi } from "../kube-json-api"; import { KubeObject } from "../kube-object"; import AbortController from "abort-controller"; import { delay } from "../../utils/delay"; +import { PassThrough } from "stream"; class TestKubeObject extends KubeObject { static kind = "Pod"; @@ -170,8 +171,7 @@ describe("KubeApi", () => { const fallbackApiBase = "/apis/extensions/v1beta1/ingresses"; const kubeApi = new KubeApi({ request, - objectConstructor: KubeObject, - apiBase, + objectConstructor: Object.assign(KubeObject, { apiBase }), fallbackApiBases: [fallbackApiBase], checkPreferredVersion: true, }); @@ -304,19 +304,28 @@ describe("KubeApi", () => { describe("watch", () => { let api: TestKubeApi; + let stream: PassThrough; beforeEach(() => { api = new TestKubeApi({ request, objectConstructor: TestKubeObject, }); + stream = new PassThrough(); + }); + + afterEach(() => { + stream.end(); + stream.destroy(); }); it("sends a valid watch request", () => { const spy = jest.spyOn(request, "getResponse"); (fetch as any).mockResponse(async () => { - return {}; + return { + body: stream, + }; }); api.watch({ namespace: "kube-system" }); @@ -327,7 +336,9 @@ describe("KubeApi", () => { const spy = jest.spyOn(request, "getResponse"); (fetch as any).mockResponse(async () => { - return {}; + return { + body: stream, + }; }); api.watch({ namespace: "kube-system", timeout: 60 }); @@ -342,11 +353,13 @@ describe("KubeApi", () => { done(); }); - return {}; + return { + body: stream, + }; }); const abortController = new AbortController(); - + api.watch({ namespace: "kube-system", timeout: 60, @@ -364,30 +377,32 @@ describe("KubeApi", () => { it("if request ended", (done) => { const spy = jest.spyOn(request, "getResponse"); + jest.spyOn(stream, "on").mockImplementation((eventName: string, callback: Function) => { + // End the request in 100ms. + if (eventName === "end") { + setTimeout(() => { + callback(); + }, 100); + } + + return stream; + }); + // we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely jest.spyOn(global, "fetch").mockImplementation(async () => { return { ok: true, - body: { - on: (eventName: string, callback: Function) => { - // End the request in 100ms. - if (eventName === "end") { - setTimeout(() => { - callback(); - }, 100); - } - }, - }, + body: stream, } as any; }); - + api.watch({ namespace: "kube-system", }); expect(spy).toHaveBeenCalledTimes(1); - setTimeout(() => { + setTimeout(() => { expect(spy).toHaveBeenCalledTimes(2); done(); }, 2000); @@ -397,11 +412,13 @@ describe("KubeApi", () => { const spy = jest.spyOn(request, "getResponse"); (fetch as any).mockResponse(async () => { - return {}; + return { + body: stream, + }; }); const timeoutSeconds = 1; - + api.watch({ namespace: "kube-system", timeout: timeoutSeconds, @@ -409,7 +426,7 @@ describe("KubeApi", () => { expect(spy).toHaveBeenCalledTimes(1); - setTimeout(() => { + setTimeout(() => { expect(spy).toHaveBeenCalledTimes(2); done(); }, timeoutSeconds * 1000 * 1.2); @@ -418,25 +435,27 @@ describe("KubeApi", () => { it("retries only once if request ends and timeout is set", (done) => { const spy = jest.spyOn(request, "getResponse"); + jest.spyOn(stream, "on").mockImplementation((eventName: string, callback: Function) => { + // End the request in 100ms. + if (eventName === "end") { + setTimeout(() => { + callback(); + }, 100); + } + + return stream; + }); + // we need to mock using jest as jest-fetch-mock doesn't support mocking the body completely jest.spyOn(global, "fetch").mockImplementation(async () => { return { ok: true, - body: { - on: (eventName: string, callback: Function) => { - // End the request in 100ms - if (eventName === "end") { - setTimeout(() => { - callback(); - }, 100); - } - }, - }, + body: stream, } as any; }); - + const timeoutSeconds = 0.5; - + api.watch({ namespace: "kube-system", timeout: timeoutSeconds, @@ -444,7 +463,7 @@ describe("KubeApi", () => { expect(spy).toHaveBeenCalledTimes(1); - setTimeout(() => { + setTimeout(() => { expect(spy).toHaveBeenCalledTimes(2); done(); }, 2000); diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 8e90efd74f..923c41c360 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -295,14 +295,18 @@ export class KubeApi { */ private async getLatestApiPrefixGroup() { // Note that this.options.apiBase is the "full" url, whereas this.apiBase is parsed - const apiBases = [this.options.apiBase, ...this.options.fallbackApiBases]; + const apiBases = [this.options.apiBase, this.objectConstructor.apiBase, ...this.options.fallbackApiBases]; for (const apiUrl of apiBases) { - // Split e.g. "/apis/extensions/v1beta1/ingresses" to parts - const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl); + if (!apiUrl) { + continue; + } - // Request available resources try { + // Split e.g. "/apis/extensions/v1beta1/ingresses" to parts + const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl); + + // Request available resources const response = await this.request.get(`${apiPrefix}/${apiVersionWithGroup}`); // If the resource is found in the group, use this apiUrl @@ -326,7 +330,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(error); + logger.error(`[KUBE-API]: ${error}`); } } diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index 5a00c5a7b4..1832cdfa10 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -219,7 +219,7 @@ export abstract class KubeObjectStore extends ItemStore case "rejected": if (onLoadFailure) { - onLoadFailure(result.reason.message); + onLoadFailure(result.reason.message || result.reason); } else { // if onLoadFailure is not provided then preserve old behaviour throw result.reason;