mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Implement KubeApi.patch (#4325)
* implement KubeApi.patch Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * cleanup tests Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com> * keep it backward compatible Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
a86b306a48
commit
23d5d40d62
@ -19,11 +19,19 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Pod, PodsApi } from "../endpoints/pods.api";
|
||||
import type { Request } from "node-fetch";
|
||||
import { forRemoteCluster, KubeApi } from "../kube-api";
|
||||
import { KubeJsonApi } from "../kube-json-api";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
class TestKubeObject extends KubeObject {
|
||||
static kind = "Pod";
|
||||
static namespaced = true;
|
||||
static apiBase = "/api/v1/pods";
|
||||
}
|
||||
|
||||
class TestKubeApi extends KubeApi<TestKubeObject> {}
|
||||
|
||||
describe("forRemoteCluster", () => {
|
||||
it("builds api client for KubeObject", async () => {
|
||||
const api = forRemoteCluster({
|
||||
@ -33,7 +41,7 @@ describe("forRemoteCluster", () => {
|
||||
user: {
|
||||
token: "daa",
|
||||
},
|
||||
}, Pod);
|
||||
}, TestKubeObject);
|
||||
|
||||
expect(api).toBeInstanceOf(KubeApi);
|
||||
});
|
||||
@ -46,9 +54,9 @@ describe("forRemoteCluster", () => {
|
||||
user: {
|
||||
token: "daa",
|
||||
},
|
||||
}, Pod, PodsApi);
|
||||
}, TestKubeObject, TestKubeApi);
|
||||
|
||||
expect(api).toBeInstanceOf(PodsApi);
|
||||
expect(api).toBeInstanceOf(TestKubeApi);
|
||||
});
|
||||
|
||||
it("calls right api endpoint", async () => {
|
||||
@ -59,7 +67,7 @@ describe("forRemoteCluster", () => {
|
||||
user: {
|
||||
token: "daa",
|
||||
},
|
||||
}, Pod);
|
||||
}, TestKubeObject);
|
||||
|
||||
(fetch as any).mockResponse(async (request: any) => {
|
||||
expect(request.url).toEqual("https://127.0.0.1:6443/api/v1/pods");
|
||||
@ -167,4 +175,63 @@ describe("KubeApi", () => {
|
||||
expect(kubeApi.apiPrefix).toEqual("/apis");
|
||||
expect(kubeApi.apiGroup).toEqual("extensions");
|
||||
});
|
||||
|
||||
describe("patch", () => {
|
||||
let api: TestKubeApi;
|
||||
|
||||
beforeEach(() => {
|
||||
api = new TestKubeApi({
|
||||
request,
|
||||
objectConstructor: TestKubeObject,
|
||||
});
|
||||
});
|
||||
|
||||
it("sends strategic patch by default", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
(fetch as any).mockResponse(async (request: 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 }}));
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, {
|
||||
spec: { replicas: 2 },
|
||||
});
|
||||
});
|
||||
|
||||
it("allows to use merge patch", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
(fetch as any).mockResponse(async (request: 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 }}));
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, {
|
||||
spec: { replicas: 2 },
|
||||
}, "merge");
|
||||
});
|
||||
|
||||
it("allows to use json patch", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
(fetch as any).mockResponse(async (request: 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 }]));
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
await api.patch({ name: "test", namespace: "default" }, [
|
||||
{ op: "replace", path: "/spec/replicas", value: 2 },
|
||||
], "json");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -37,6 +37,7 @@ import { noop } from "../utils";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import AbortController from "abort-controller";
|
||||
import { Agent, AgentOptions } from "https";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
export interface IKubeApiOptions<T extends KubeObject> {
|
||||
/**
|
||||
@ -207,6 +208,14 @@ export type KubeApiWatchOptions = {
|
||||
retry?: boolean;
|
||||
};
|
||||
|
||||
export type KubeApiPatchType = "merge" | "json" | "strategic";
|
||||
|
||||
const patchTypeHeaders: Record<KubeApiPatchType, string> = {
|
||||
"merge": "application/merge-patch+json",
|
||||
"json": "application/json-patch+json",
|
||||
"strategic": "application/strategic-merge-patch+json",
|
||||
};
|
||||
|
||||
export class KubeApi<T extends KubeObject> {
|
||||
readonly kind: string;
|
||||
readonly apiBase: string;
|
||||
@ -475,6 +484,24 @@ export class KubeApi<T extends KubeObject> {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async patch({ name = "", namespace = "default" } = {}, data?: Partial<T> | Patch, strategy: KubeApiPatchType = "strategic"): Promise<T | null> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl({ namespace, name });
|
||||
|
||||
const res = await this.request.patch(apiUrl, { data }, {
|
||||
headers: {
|
||||
"content-type": patchTypeHeaders[strategy],
|
||||
},
|
||||
});
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
if (Array.isArray(parsed)) {
|
||||
throw new Error(`PATCH request to ${apiUrl} returned an array: ${JSON.stringify(parsed)}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async delete({ name = "", namespace = "default" }) {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl({ namespace, name });
|
||||
|
||||
@ -295,16 +295,30 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
||||
}
|
||||
|
||||
async patch(item: T, patch: Patch): Promise<T> {
|
||||
return this.postUpdate(await item.patch(patch));
|
||||
return this.postUpdate(
|
||||
await this.api.patch(
|
||||
{
|
||||
name: item.getName(), namespace: item.getNs(),
|
||||
},
|
||||
patch,
|
||||
"json",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async update(item: T, data: Partial<T>): Promise<T> {
|
||||
return this.postUpdate(await item.update(data));
|
||||
return this.postUpdate(
|
||||
await this.api.update(
|
||||
{
|
||||
name: item.getName(), namespace: item.getNs(),
|
||||
},
|
||||
data,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async remove(item: T) {
|
||||
await item.delete();
|
||||
this.items.remove(item);
|
||||
await this.api.delete({ name: item.getName(), namespace: item.getNs() });
|
||||
this.selectedItemsIds.delete(item.getId());
|
||||
}
|
||||
|
||||
|
||||
@ -306,6 +306,9 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use KubeApi.patch instead
|
||||
*/
|
||||
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
|
||||
for (const op of patch) {
|
||||
if (KubeObject.nonEditablePaths.has(op.path)) {
|
||||
@ -328,6 +331,8 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
* Note: this is brittle if `data` is not actually partial (but instead whole).
|
||||
* As fields such as `resourceVersion` will probably out of date. This is a
|
||||
* common race condition.
|
||||
*
|
||||
* @deprecated use KubeApi.update instead
|
||||
*/
|
||||
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
||||
// use unified resource-applier api for updating all k8s objects
|
||||
@ -337,6 +342,9 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use KubeApi.delete instead
|
||||
*/
|
||||
delete(params?: JsonApiParams) {
|
||||
return apiKube.del(this.selfLink, params);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user