mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove jest-fetch-mock and make fetch injectable
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
5a2a9248e8
commit
b1d1b69b3d
13 files changed
+408
-336
No files matched your search
@@ -2,37 +2,40 @@
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { forRemoteCluster, KubeApi } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { KubeJsonApi } from "../kube-json-api";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { delay } from "../../utils/delay";
|
||||
import { PassThrough } from "stream";
|
||||
import { ApiManager } from "../api-manager";
|
||||
import type { FetchMock } from "jest-fetch-mock/types";
|
||||
import type { ApiManager } from "../api-manager";
|
||||
import { DeploymentApi, Ingress, IngressApi, Pod, PodApi } from "../endpoints";
|
||||
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
|
||||
import apiManagerInjectable from "../api-manager/manager.injectable";
|
||||
import autoRegistrationInjectable from "../api-manager/auto-registration.injectable";
|
||||
import type { JsonApiDependencies } from "../json-api";
|
||||
import loggerInjectable from "../../logger.injectable";
|
||||
import type { Fetch } from "../../fetch/fetch.injectable";
|
||||
import fetchInjectable from "../../fetch/fetch.injectable";
|
||||
import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remote-cluster.injectable";
|
||||
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
|
||||
import { Headers, Response } from "node-fetch";
|
||||
import AbortController from "abort-controller";
|
||||
|
||||
jest.mock("../api-manager");
|
||||
|
||||
const mockFetch = fetch as FetchMock;
|
||||
|
||||
describe("forRemoteCluster", () => {
|
||||
let apiManager: jest.Mocked<ApiManager>;
|
||||
describe("createKubeApiForRemoteCluster", () => {
|
||||
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
|
||||
let fetchMock: jest.MockedFunction<Fetch>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
|
||||
fetchMock = jest.fn();
|
||||
di.override(fetchInjectable, () => fetchMock);
|
||||
|
||||
di.override(apiManagerInjectable, () => apiManager);
|
||||
createKubeApiForRemoteCluster = di.inject(createKubeApiForRemoteClusterInjectable);
|
||||
});
|
||||
|
||||
it("builds api client for KubeObject", async () => {
|
||||
const api = forRemoteCluster({
|
||||
const api = createKubeApiForRemoteCluster({
|
||||
cluster: {
|
||||
server: "https://127.0.0.1:6443",
|
||||
},
|
||||
@@ -45,7 +48,7 @@ describe("forRemoteCluster", () => {
|
||||
});
|
||||
|
||||
it("builds api client for given KubeApi", async () => {
|
||||
const api = forRemoteCluster({
|
||||
const api = createKubeApiForRemoteCluster({
|
||||
cluster: {
|
||||
server: "https://127.0.0.1:6443",
|
||||
},
|
||||
@@ -58,7 +61,7 @@ describe("forRemoteCluster", () => {
|
||||
});
|
||||
|
||||
it("calls right api endpoint", async () => {
|
||||
const api = forRemoteCluster({
|
||||
const api = createKubeApiForRemoteCluster({
|
||||
cluster: {
|
||||
server: "https://127.0.0.1:6443",
|
||||
},
|
||||
@@ -67,65 +70,42 @@ describe("forRemoteCluster", () => {
|
||||
},
|
||||
}, Pod);
|
||||
|
||||
mockFetch.mockResponse(async (request: any) => {
|
||||
expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods");
|
||||
fetchMock.mockImplementation(async (url) => {
|
||||
expect(url).toBe("https://127.0.0.1:6443/api/v1/pods");
|
||||
|
||||
return {
|
||||
body: "hello",
|
||||
};
|
||||
return new Response("hello");
|
||||
});
|
||||
|
||||
expect.hasAssertions();
|
||||
|
||||
await api.list();
|
||||
expect(await api.list()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("KubeApi", () => {
|
||||
let request: KubeJsonApi;
|
||||
let apiManager: jest.Mocked<ApiManager>;
|
||||
let registerApiSpy: jest.SpiedFunction<ApiManager["registerApi"]>;
|
||||
let fetchMock: jest.MockedFunction<Fetch>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
request = new KubeJsonApi({
|
||||
fetchMock = jest.fn();
|
||||
di.override(fetchInjectable, () => fetchMock);
|
||||
|
||||
const dependencies: JsonApiDependencies = {
|
||||
logger: di.inject(loggerInjectable),
|
||||
fetch: di.inject(fetchInjectable),
|
||||
};
|
||||
|
||||
request = new KubeJsonApi(dependencies, {
|
||||
serverAddress: `http://127.0.0.1:9999`,
|
||||
apiBase: "/api-kube",
|
||||
});
|
||||
apiManager = new ApiManager() as jest.Mocked<ApiManager>;
|
||||
registerApiSpy = jest.spyOn(di.inject(apiManagerInjectable), "registerApi");
|
||||
|
||||
di.override(apiManagerInjectable, () => apiManager);
|
||||
di.inject(autoRegistrationInjectable);
|
||||
});
|
||||
|
||||
it("uses url from apiBase if apiBase contains the resource", async () => {
|
||||
mockFetch.mockResponse(async (request: any) => {
|
||||
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}),
|
||||
};
|
||||
} else if (request.url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
|
||||
// Even if the old API contains ingresses, KubeApi should prefer the apiBase url
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [],
|
||||
}),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const apiBase = "/apis/networking.k8s.io/v1/ingresses";
|
||||
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
|
||||
const kubeApi = new IngressApi({
|
||||
@@ -136,6 +116,46 @@ describe("KubeApi", () => {
|
||||
checkPreferredVersion: true,
|
||||
});
|
||||
|
||||
fetchMock.mockImplementation(async (url) => {
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}));
|
||||
}
|
||||
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}));
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ resources: [] }));
|
||||
});
|
||||
|
||||
fetchMock.mockImplementation(async (url) => {
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}));
|
||||
}
|
||||
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}));
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ resources: [] }));
|
||||
});
|
||||
|
||||
await kubeApi.get({
|
||||
name: "foo",
|
||||
namespace: "default",
|
||||
@@ -145,30 +165,6 @@ describe("KubeApi", () => {
|
||||
});
|
||||
|
||||
it("uses url from fallbackApiBases if apiBase lacks the resource", async () => {
|
||||
mockFetch.mockResponse(async (request: any) => {
|
||||
if (request.url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [],
|
||||
}),
|
||||
};
|
||||
} else if (request.url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
resources: [],
|
||||
}),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const apiBase = "apis/networking.k8s.io/v1/ingresses";
|
||||
const fallbackApiBase = "/apis/extensions/v1beta1/ingresses";
|
||||
const kubeApi = new IngressApi({
|
||||
@@ -179,10 +175,30 @@ describe("KubeApi", () => {
|
||||
checkPreferredVersion: true,
|
||||
});
|
||||
|
||||
|
||||
fetchMock.mockImplementation(async (url) => {
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/networking.k8s.io/v1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [],
|
||||
}));
|
||||
}
|
||||
|
||||
if (url === "http://127.0.0.1:9999/api-kube/apis/extensions/v1beta1") {
|
||||
return new Response(JSON.stringify({
|
||||
resources: [{
|
||||
name: "ingresses",
|
||||
}],
|
||||
}));
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ resources: [] }));
|
||||
});
|
||||
|
||||
await kubeApi.get({
|
||||
name: "foo",
|
||||
namespace: "default",
|
||||
});
|
||||
|
||||
expect(kubeApi.apiPrefix).toEqual("/apis");
|
||||
expect(kubeApi.apiGroup).toEqual("extensions");
|
||||
});
|
||||
@@ -225,7 +241,7 @@ describe("KubeApi", () => {
|
||||
await (api as any).checkPreferredVersion();
|
||||
|
||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
||||
expect(apiManager.registerApi).toBeCalledWith(api);
|
||||
expect(registerApiSpy).toBeCalledWith(api);
|
||||
});
|
||||
|
||||
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
|
||||
@@ -265,7 +281,7 @@ describe("KubeApi", () => {
|
||||
await (api as any).checkPreferredVersion();
|
||||
|
||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
||||
expect(apiManager.registerApi).toBeCalledWith(api);
|
||||
expect(registerApiSpy).toBeCalledWith(api);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -281,12 +297,12 @@ describe("KubeApi", () => {
|
||||
it("sends strategic patch by default", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("PATCH");
|
||||
expect(request.headers.get("content-type")).toMatch("strategic-merge-patch");
|
||||
expect(request.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("patch");
|
||||
expect(new Headers(init?.headers).get("content-type")).toMatch("strategic-merge-patch");
|
||||
expect(init?.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, {
|
||||
@@ -297,12 +313,12 @@ describe("KubeApi", () => {
|
||||
it("allows to use merge patch", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("PATCH");
|
||||
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
||||
expect(request.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("patch");
|
||||
expect(new Headers(init?.headers).get("content-type")).toMatch("merge-patch");
|
||||
expect(init?.body?.toString()).toEqual(JSON.stringify({ spec: { replicas: 2 }}));
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, {
|
||||
@@ -313,12 +329,12 @@ describe("KubeApi", () => {
|
||||
it("allows to use json patch", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("PATCH");
|
||||
expect(request.headers.get("content-type")).toMatch("json-patch");
|
||||
expect(request.body?.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("patch");
|
||||
expect(new Headers(init?.headers).get("content-type")).toMatch("json-patch");
|
||||
expect(init?.body?.toString()).toEqual(JSON.stringify([{ op: "replace", path: "/spec/replicas", value: 2 }]));
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, [
|
||||
@@ -329,12 +345,12 @@ describe("KubeApi", () => {
|
||||
it("allows deep partial patch", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("PATCH");
|
||||
expect(request.headers.get("content-type")).toMatch("merge-patch");
|
||||
expect(request.body?.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("patch");
|
||||
expect(new Headers(init?.headers).get("content-type")).toMatch("merge-patch");
|
||||
expect(init?.body?.toString()).toEqual(JSON.stringify({ metadata: { annotations: { provisioned: "true" }}}));
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.patch(
|
||||
@@ -357,11 +373,11 @@ describe("KubeApi", () => {
|
||||
|
||||
it("sends correct request with empty namespace", async () => {
|
||||
expect.hasAssertions();
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("DELETE");
|
||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/pods/foo?propagationPolicy=Background");
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("delete");
|
||||
expect(url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/pods/foo?propagationPolicy=Background");
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.delete({ name: "foo", namespace: "" });
|
||||
@@ -369,11 +385,11 @@ describe("KubeApi", () => {
|
||||
|
||||
it("sends correct request without namespace", async () => {
|
||||
expect.hasAssertions();
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("DELETE");
|
||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background");
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("delete");
|
||||
expect(url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/default/pods/foo?propagationPolicy=Background");
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.delete({ name: "foo" });
|
||||
@@ -381,11 +397,11 @@ describe("KubeApi", () => {
|
||||
|
||||
it("sends correct request with namespace", async () => {
|
||||
expect.hasAssertions();
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("DELETE");
|
||||
expect(request.url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods/foo?propagationPolicy=Background");
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("delete");
|
||||
expect(url).toEqual("http://127.0.0.1:9999/api-kube/api/v1/namespaces/kube-system/pods/foo?propagationPolicy=Background");
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.delete({ name: "foo", namespace: "kube-system" });
|
||||
@@ -393,11 +409,11 @@ describe("KubeApi", () => {
|
||||
|
||||
it("allows to change propagationPolicy", async () => {
|
||||
expect.hasAssertions();
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("DELETE");
|
||||
expect(request.url).toMatch("propagationPolicy=Orphan");
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("delete");
|
||||
expect(url).toMatch("propagationPolicy=Orphan");
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.delete({ name: "foo", namespace: "default", propagationPolicy: "Orphan" });
|
||||
@@ -424,11 +440,8 @@ describe("KubeApi", () => {
|
||||
it("sends a valid watch request", () => {
|
||||
const spy = jest.spyOn(request, "getResponse");
|
||||
|
||||
mockFetch.mockResponse(async () => {
|
||||
return {
|
||||
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
|
||||
body: stream as unknown as string,
|
||||
};
|
||||
fetchMock.mockImplementation(async () => {
|
||||
return new Response(stream);
|
||||
});
|
||||
|
||||
api.watch({ namespace: "kube-system" });
|
||||
@@ -438,11 +451,8 @@ describe("KubeApi", () => {
|
||||
it("sends timeout as a query parameter", async () => {
|
||||
const spy = jest.spyOn(request, "getResponse");
|
||||
|
||||
mockFetch.mockResponse(async () => {
|
||||
return {
|
||||
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
|
||||
body: stream as unknown as string,
|
||||
};
|
||||
fetchMock.mockImplementation(async () => {
|
||||
return new Response(stream);
|
||||
});
|
||||
|
||||
api.watch({ namespace: "kube-system", timeout: 60 });
|
||||
@@ -452,15 +462,12 @@ describe("KubeApi", () => {
|
||||
it("aborts watch using abortController", (done) => {
|
||||
const spy = jest.spyOn(request, "getResponse");
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
request.signal.addEventListener("abort", () => {
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
init?.signal?.addEventListener("abort", () => {
|
||||
done();
|
||||
});
|
||||
|
||||
return {
|
||||
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
|
||||
body: stream as unknown as string,
|
||||
};
|
||||
return new Response(stream);
|
||||
});
|
||||
|
||||
const abortController = new AbortController();
|
||||
@@ -490,12 +497,10 @@ describe("KubeApi", () => {
|
||||
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: stream as never,
|
||||
} as Partial<Response> as Response;
|
||||
fetchMock.mockImplementation(async () => {
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
api.watch({
|
||||
@@ -513,11 +518,8 @@ describe("KubeApi", () => {
|
||||
it("if request not closed after timeout", (done) => {
|
||||
const spy = jest.spyOn(request, "getResponse");
|
||||
|
||||
mockFetch.mockResponse(async () => {
|
||||
return {
|
||||
// needed for https://github.com/jefflau/jest-fetch-mock/issues/218
|
||||
body: stream as unknown as string,
|
||||
};
|
||||
fetchMock.mockImplementation(async () => {
|
||||
return new Response(stream);
|
||||
});
|
||||
|
||||
const timeoutSeconds = 1;
|
||||
@@ -550,11 +552,10 @@ describe("KubeApi", () => {
|
||||
});
|
||||
|
||||
// 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: stream as never,
|
||||
} as Partial<Response> as Response;
|
||||
fetchMock.mockImplementation(async () => {
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
const timeoutSeconds = 0.5;
|
||||
@@ -591,9 +592,9 @@ describe("KubeApi", () => {
|
||||
it("should add kind and apiVersion", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("POST");
|
||||
expect(JSON.parse(String(request.body))).toEqual({
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("post");
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
metadata: {
|
||||
@@ -617,7 +618,7 @@ describe("KubeApi", () => {
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.create({
|
||||
@@ -645,9 +646,9 @@ describe("KubeApi", () => {
|
||||
it("doesn't override metadata.labels", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("POST");
|
||||
expect(JSON.parse(String(request.body))).toEqual({
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("post");
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
metadata: {
|
||||
@@ -659,7 +660,7 @@ describe("KubeApi", () => {
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.create({
|
||||
@@ -688,9 +689,9 @@ describe("KubeApi", () => {
|
||||
it("doesn't override metadata.labels", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
mockFetch.mockResponse(async request => {
|
||||
expect(request.method).toEqual("PUT");
|
||||
expect(JSON.parse(String(request.body))).toEqual({
|
||||
fetchMock.mockImplementation(async (url, init) => {
|
||||
expect(init?.method).toEqual("put");
|
||||
expect(JSON.parse(String(init?.body))).toEqual({
|
||||
metadata: {
|
||||
name: "foobar",
|
||||
namespace: "default",
|
||||
@@ -700,7 +701,7 @@ describe("KubeApi", () => {
|
||||
},
|
||||
});
|
||||
|
||||
return {};
|
||||
return new Response();
|
||||
});
|
||||
|
||||
await api.update({
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fetchInjectable from "../fetch/fetch.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import { apiKubePrefix } from "../vars";
|
||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||
import { apiBaseInjectionToken } from "./api-base";
|
||||
import type { JsonApiDependencies } from "./json-api";
|
||||
import type { KubeApiOptions } from "./kube-api";
|
||||
import { KubeApi } from "./kube-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
|
||||
|
||||
export interface CreateKubeApiForLocalClusterConfig {
|
||||
metadata: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateKubeApiForCluster {
|
||||
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
|
||||
cluster: CreateKubeApiForLocalClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api
|
||||
): Api;
|
||||
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
|
||||
cluster: CreateKubeApiForLocalClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>
|
||||
): KubeApi<Object>;
|
||||
}
|
||||
|
||||
const createKubeApiForClusterInjectable = getInjectable({
|
||||
id: "create-kube-api-for-cluster",
|
||||
instantiate: (di): CreateKubeApiForCluster => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const dependencies: JsonApiDependencies = {
|
||||
fetch: di.inject(fetchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (cluster: CreateKubeApiForLocalClusterConfig, kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>, apiClass = KubeApi) => {
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
const request = new KubeJsonApi(dependencies, {
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${cluster.metadata.uid}.localhost:${url.port}`,
|
||||
},
|
||||
});
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass,
|
||||
request,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeApiForClusterInjectable;
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { AgentOptions } from "https";
|
||||
import { Agent } from "https";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import fetchInjectable from "../fetch/fetch.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||
import type { JsonApiDependencies } from "./json-api";
|
||||
import type { KubeApiOptions } from "./kube-api";
|
||||
import { KubeApi } from "./kube-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
|
||||
|
||||
export interface CreateKubeApiForRemoteClusterConfig {
|
||||
cluster: {
|
||||
server: string;
|
||||
caData?: string;
|
||||
skipTLSVerify?: boolean;
|
||||
};
|
||||
user: {
|
||||
token?: string | (() => Promise<string>);
|
||||
clientCertificateData?: string;
|
||||
clientKeyData?: string;
|
||||
};
|
||||
/**
|
||||
* Custom instance of https.agent to use for the requests
|
||||
*
|
||||
* @remarks the custom agent replaced default agent, options skipTLSVerify,
|
||||
* clientCertificateData, clientKeyData and caData are ignored.
|
||||
*/
|
||||
agent?: Agent;
|
||||
}
|
||||
|
||||
export interface CreateKubeApiForRemoteCluster {
|
||||
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
|
||||
config: CreateKubeApiForRemoteClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api,
|
||||
): Api;
|
||||
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
|
||||
config: CreateKubeApiForRemoteClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>,
|
||||
): KubeApi<Object>;
|
||||
}
|
||||
|
||||
const createKubeApiForRemoteClusterInjectable = getInjectable({
|
||||
id: "create-kube-api-for-remote-cluster",
|
||||
instantiate: (di): CreateKubeApiForRemoteCluster => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const dependencies: JsonApiDependencies = {
|
||||
fetch: di.inject(fetchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (config: CreateKubeApiForRemoteClusterConfig, kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>, apiClass = KubeApi) => {
|
||||
const reqInit: RequestInit = {};
|
||||
const agentOptions: AgentOptions = {};
|
||||
|
||||
if (config.cluster.skipTLSVerify === true) {
|
||||
agentOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
if (config.user.clientCertificateData) {
|
||||
agentOptions.cert = config.user.clientCertificateData;
|
||||
}
|
||||
|
||||
if (config.user.clientKeyData) {
|
||||
agentOptions.key = config.user.clientKeyData;
|
||||
}
|
||||
|
||||
if (config.cluster.caData) {
|
||||
agentOptions.ca = config.cluster.caData;
|
||||
}
|
||||
|
||||
if (Object.keys(agentOptions).length > 0) {
|
||||
reqInit.agent = new Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (config.agent) {
|
||||
reqInit.agent = config.agent;
|
||||
}
|
||||
|
||||
const token = config.user.token;
|
||||
const request = new KubeJsonApi(dependencies, {
|
||||
serverAddress: config.cluster.server,
|
||||
apiBase: "",
|
||||
debug: isDevelopment,
|
||||
...(token ? {
|
||||
getRequestOptions: async () => ({
|
||||
headers: {
|
||||
"Authorization": `Bearer ${typeof token === "function" ? await token() : token}`,
|
||||
},
|
||||
}),
|
||||
} : {}),
|
||||
}, reqInit);
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass,
|
||||
request,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeApiForRemoteClusterInjectable;
|
||||
@@ -3,8 +3,11 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fetchInjectable from "../fetch/fetch.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import { apiKubePrefix, isDebugging } from "../vars";
|
||||
import { apiBaseInjectionToken } from "./api-base";
|
||||
import type { JsonApiDependencies } from "./json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
|
||||
export type CreateKubeJsonApiForCluster = (clusterId: string) => KubeJsonApi;
|
||||
@@ -13,11 +16,15 @@ const createKubeJsonApiForClusterInjectable = getInjectable({
|
||||
id: "create-kube-json-api-for-cluster",
|
||||
instantiate: (di): CreateKubeJsonApiForCluster => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
const dependencies: JsonApiDependencies = {
|
||||
fetch: di.inject(fetchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (clusterId) => {
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
|
||||
return new KubeJsonApi({
|
||||
return new KubeJsonApi(dependencies, {
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDebugging,
|
||||
|
||||
@@ -9,12 +9,12 @@ import { Agent as HttpAgent } from "http";
|
||||
import { Agent as HttpsAgent } from "https";
|
||||
import { merge } from "lodash";
|
||||
import type { Response, RequestInit } from "node-fetch";
|
||||
import fetch from "node-fetch";
|
||||
import { stringify } from "querystring";
|
||||
import type { Patch } from "rfc6902";
|
||||
import type { PartialDeep, ValueOf } from "type-fest";
|
||||
import { EventEmitter } from "../../common/event-emitter";
|
||||
import logger from "../../common/logger";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { Fetch } from "../fetch/fetch.injectable";
|
||||
import type { Defaulted } from "../utils";
|
||||
import { json } from "../utils";
|
||||
|
||||
@@ -59,6 +59,11 @@ export type ParamsAndQuery<Params, Query> = (
|
||||
: Params & { query?: undefined }
|
||||
);
|
||||
|
||||
export interface JsonApiDependencies {
|
||||
fetch: Fetch;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>> {
|
||||
static readonly reqInitDefault = {
|
||||
headers: {
|
||||
@@ -71,7 +76,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
debug: false,
|
||||
};
|
||||
|
||||
constructor(public readonly config: JsonApiConfig, reqInit?: RequestInit) {
|
||||
constructor(protected readonly dependencies: JsonApiDependencies, public readonly config: JsonApiConfig, reqInit?: RequestInit) {
|
||||
this.config = Object.assign({}, JsonApi.configDefault, config);
|
||||
this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit);
|
||||
this.parseResponse = this.parseResponse.bind(this);
|
||||
@@ -105,7 +110,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
|
||||
}
|
||||
|
||||
return fetch(reqUrl, reqInit);
|
||||
return this.dependencies.fetch(reqUrl, reqInit);
|
||||
}
|
||||
|
||||
get<OutData = Data, Query = QueryParams>(
|
||||
@@ -177,7 +182,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
reqInit,
|
||||
};
|
||||
|
||||
const res = await fetch(reqUrl, reqInit);
|
||||
const res = await this.dependencies.fetch(reqUrl, reqInit);
|
||||
|
||||
return this.parseResponse<OutData>(res, infoLog);
|
||||
}
|
||||
@@ -233,7 +238,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
protected writeLog(log: JsonApiLog) {
|
||||
const { method, reqUrl, ...params } = log;
|
||||
|
||||
logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
|
||||
this.dependencies.logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,18 @@
|
||||
|
||||
// Base class for building all kubernetes apis
|
||||
|
||||
import { isFunction, merge } from "lodash";
|
||||
import { merge } from "lodash";
|
||||
import { stringify } from "querystring";
|
||||
import { apiKubePrefix, isDevelopment } from "../../common/vars";
|
||||
import { apiBase, apiKube } from "./index";
|
||||
import { apiKube } from "./index";
|
||||
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
||||
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata } from "./kube-object";
|
||||
import { KubeObject, KubeStatus, isKubeStatusData } from "./kube-object";
|
||||
import byline from "byline";
|
||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||
import type { KubeJsonApiData } from "./kube-json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
|
||||
import type { Disposer } from "../utils";
|
||||
import { isDefined, noop, WrappedAbortController } from "../utils";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import type { AgentOptions } from "https";
|
||||
import { Agent } from "https";
|
||||
import type { Patch } from "rfc6902";
|
||||
import assert from "assert";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
@@ -145,136 +141,8 @@ export interface KubeApiResourceList {
|
||||
resources: KubeApiResource[];
|
||||
}
|
||||
|
||||
export interface ILocalKubeApiConfig {
|
||||
metadata: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface IKubeApiCluster extends ILocalKubeApiConfig { }
|
||||
|
||||
export interface IRemoteKubeApiConfig {
|
||||
cluster: {
|
||||
server: string;
|
||||
caData?: string;
|
||||
skipTLSVerify?: boolean;
|
||||
};
|
||||
user: {
|
||||
token?: string | (() => Promise<string>);
|
||||
clientCertificateData?: string;
|
||||
clientKeyData?: string;
|
||||
};
|
||||
/**
|
||||
* Custom instance of https.agent to use for the requests
|
||||
*
|
||||
* @remarks the custom agent replaced default agent, options skipTLSVerify,
|
||||
* clientCertificateData, clientKeyData and caData are ignored.
|
||||
*/
|
||||
agent?: Agent;
|
||||
}
|
||||
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => Api): Api;
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>): KubeApi<Object>;
|
||||
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: (new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>) = KubeApi): KubeApi<Object> {
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
const request = new KubeJsonApi({
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${cluster.metadata.uid}.localhost:${url.port}`,
|
||||
},
|
||||
});
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass as KubeObjectConstructor<Object, KubeJsonApiDataFor<Object>>,
|
||||
request,
|
||||
});
|
||||
}
|
||||
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => Api): Api;
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>): KubeApi<Object>;
|
||||
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object> = KubeApi): KubeApi<Object> {
|
||||
const reqInit: RequestInit = {};
|
||||
const agentOptions: AgentOptions = {};
|
||||
|
||||
if (config.cluster.skipTLSVerify === true) {
|
||||
agentOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
if (config.user.clientCertificateData) {
|
||||
agentOptions.cert = config.user.clientCertificateData;
|
||||
}
|
||||
|
||||
if (config.user.clientKeyData) {
|
||||
agentOptions.key = config.user.clientKeyData;
|
||||
}
|
||||
|
||||
if (config.cluster.caData) {
|
||||
agentOptions.ca = config.cluster.caData;
|
||||
}
|
||||
|
||||
if (Object.keys(agentOptions).length > 0) {
|
||||
reqInit.agent = new Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (config.agent) {
|
||||
reqInit.agent = config.agent;
|
||||
}
|
||||
|
||||
const token = config.user.token;
|
||||
const request = new KubeJsonApi({
|
||||
serverAddress: config.cluster.server,
|
||||
apiBase: "",
|
||||
debug: isDevelopment,
|
||||
...(token ? {
|
||||
getRequestOptions: async () => ({
|
||||
headers: {
|
||||
"Authorization": `Bearer ${isFunction(token) ? await token() : token}`,
|
||||
},
|
||||
}),
|
||||
} : {}),
|
||||
}, reqInit);
|
||||
|
||||
if (!apiClass) {
|
||||
apiClass = KubeApi as new (apiOpts: KubeApiOptions<Object>) => Api;
|
||||
}
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass as KubeObjectConstructor<Object, KubeJsonApiDataFor<Object>>,
|
||||
request,
|
||||
});
|
||||
}
|
||||
|
||||
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (data: IKubeWatchEvent<T>, error: any) => void;
|
||||
|
||||
export interface KubeApiWatchOptions<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>> {
|
||||
|
||||
Reference in new issue
Block a user