1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

chore: Change refactoring of implementation to maintain behaviour

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-05-01 16:08:41 -04:00
parent 8395c262ad
commit dcc5495312
8 changed files with 103 additions and 94 deletions

View File

@ -9,33 +9,35 @@ import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
import type { StatefulSetApi } from "../endpoints";
import statefulSetApiInjectable from "../endpoints/stateful-set.api.injectable";
import type { KubeJsonApi } from "../kube-json-api";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import { flushPromises } from "@k8slens/test-utils";
describe("StatefulSetApi", () => {
let statefulSetApi: StatefulSetApi;
let kubeJsonApi: jest.Mocked<KubeJsonApi>;
let kubeJsonApiPatchMock: AsyncFnMock<KubeJsonApi["patch"]>;
let kubeJsonApiGetMock: AsyncFnMock<KubeJsonApi["get"]>;
beforeEach(() => {
const di = getDiForUnitTesting();
di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = {
getResponse: jest.fn(),
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
del: jest.fn(),
} as never;
di.override(apiKubeInjectable, () => kubeJsonApi);
kubeJsonApiPatchMock = asyncFn();
kubeJsonApiGetMock = asyncFn();
di.override(apiKubeInjectable, () => ({
get: kubeJsonApiGetMock,
patch: kubeJsonApiPatchMock,
} as Partial<KubeJsonApi> as KubeJsonApi));
statefulSetApi = di.inject(statefulSetApiInjectable);
});
describe("scale", () => {
it("requests Kubernetes API with PATCH verb and correct amount of replicas", () => {
statefulSetApi.scale({ namespace: "default", name: "statefulset-1" }, 5);
it("requests Kubernetes API with PATCH verb and correct amount of replicas", async () => {
const req = statefulSetApi.scale({ namespace: "default", name: "statefulset-1" }, 5);
expect(kubeJsonApi.patch).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/statefulsets/statefulset-1/scale", {
await flushPromises();
expect(kubeJsonApiPatchMock).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/statefulsets/statefulset-1/scale", {
data: {
spec: {
replicas: 5,
@ -47,6 +49,19 @@ describe("StatefulSetApi", () => {
"content-type": "application/merge-patch+json",
},
});
await kubeJsonApiPatchMock.resolve({});
await req;
});
it("requests Kubernetes API with GET verb and correct sub-resource", async () => {
const req = statefulSetApi.getReplicas({ namespace: "default", name: "statefulset-1" });
await flushPromises();
expect(kubeJsonApiGetMock).toHaveBeenCalledWith("/apis/apps/v1/namespaces/default/statefulsets/statefulset-1/scale");
await kubeJsonApiGetMock.resolve({ status: { replicas: 10 }});
expect(await req).toBe(10);
});
});
});

View File

@ -8,7 +8,6 @@ import moment from "moment";
import type { DerivedKubeApiOptions, KubeApiDependencies, NamespacedResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import { Deployment } from "@k8slens/kube-object";
import { hasTypedProperty, isNumber, isObject } from "@k8slens/utilities";
export class DeploymentApi extends KubeApi<Deployment> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
@ -18,33 +17,14 @@ export class DeploymentApi extends KubeApi<Deployment> {
});
}
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
const { status } = await this.getResourceScale(params);
if (isObject(status) && hasTypedProperty(status, "replicas", isNumber)) {
return status.replicas;
}
return 0;
return status.replicas;
}
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
spec: {
replicas,
},
},
},
{
headers: {
"content-type": "application/merge-patch+json",
},
});
return this.scaleResource(params, { spec: { replicas }});
}
restart(params: NamespacedResourceDescriptor) {

View File

@ -15,24 +15,13 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
});
}
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const { status } = await this.request.get(this.getScaleApiUrl(params));
const { status } = await this.getResourceScale(params);
return (status as { replicas: number })?.replicas;
return status.replicas;
}
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.request.put(this.getScaleApiUrl(params), {
data: {
metadata: params,
spec: {
replicas,
},
},
});
return this.scaleResource(params, { spec: { replicas }});
}
}

View File

@ -25,17 +25,6 @@ export class ReplicationControllerApi extends KubeApi<ReplicationController> {
}
scale(params: NamespacedResourceDescriptor, replicas: number): Promise<Scale> {
return this.request.patch(this.getScaleApiUrl(params), {
data: {
metadata: params,
spec: {
replicas,
},
},
}, {
headers: {
"content-type": "application/strategic-merge-patch+json",
},
});
return this.scaleResource(params, { spec: { replicas }});
}
}

View File

@ -17,23 +17,14 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
});
}
protected getScaleApiUrl(params: NamespacedResourceDescriptor) {
return `${this.formatUrlForNotListing(params)}/scale`;
}
async getReplicas(params: NamespacedResourceDescriptor): Promise<number> {
const apiUrl = this.getScaleApiUrl(params);
const { status = 0 } = await this.request.get(apiUrl) as { status?: number };
const { status } = await this.getResourceScale(params);
return status;
return status.replicas;
}
scale(params: NamespacedResourceDescriptor, replicas: number) {
return this.patch(params, {
spec: {
replicas,
},
}, "merge");
return this.scaleResource(params, { spec: { replicas }});
}
restart(params: NamespacedResourceDescriptor) {

View File

@ -8,7 +8,7 @@
import { merge } from "lodash";
import { stringify } from "querystring";
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata, KubeJsonApiData, KubeObject, KubeObjectScope } from "@k8slens/kube-object";
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata, KubeJsonApiData, KubeObject, KubeObjectScope, Scale } from "@k8slens/kube-object";
import { isJsonApiData, isJsonApiDataList, isPartialJsonApiData, KubeStatus, isKubeStatusData } from "@k8slens/kube-object";
import byline from "byline";
import type { IKubeWatchEvent } from "./kube-watch-event";
@ -22,6 +22,7 @@ import type { PartialDeep } from "type-fest";
import type { Logger } from "../logger";
import { matches } from "lodash/fp";
import { makeObservable, observable } from "mobx";
import type { ScaleCreateOptions } from "@k8slens/kube-object/src/types/scale";
/**
* The options used for creating a `KubeApi`
@ -213,6 +214,10 @@ export interface ResourceDescriptor {
namespace?: string;
}
export interface SubResourceDescriptor {
subResource: string;
}
export type SpecificResourceDescriptor<Scope extends KubeObjectScope> = {
/**
* The name of the kubernetes resource
@ -592,6 +597,37 @@ export class KubeApi<
return parsed;
}
/**
* An internal method for requesting the `/scale` sub-resource if it exists
*/
protected async getResourceScale(desc: ResourceDescriptor): Promise<Scale> {
await this.checkPreferredVersion();
const apiUrl = this.formatUrlForNotListing(desc);
const res = await this.request.get(`${apiUrl}/scale`);
return res as Scale;
}
/**
* An internal method for requesting the `/scale` sub-resource if it exists
*/
protected async scaleResource(desc: ResourceDescriptor, data: ScaleCreateOptions): Promise<Scale> {
await this.checkPreferredVersion();
const apiUrl = this.formatUrlForNotListing(desc);
const res = await this.request.patch(`${apiUrl}/scale`, {
data: { ...data },
}, {
headers: {
"content-type": patchTypeHeaders.merge,
},
});
return res as Scale;
}
async patch(desc: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null>;
async patch(desc: ResourceDescriptor, data: PartialDeep<Object>, strategy: "strategic" | "merge"): Promise<Object | null>;
async patch(desc: ResourceDescriptor, data: Patch, strategy: "json"): Promise<Object | null>;

View File

@ -3,28 +3,10 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type {
KubeObjectMetadata,
KubeObjectStatus,
NamespaceScopedMetadata,
BaseKubeObjectCondition,
} from "../api-types";
import type { KubeObjectStatus, NamespaceScopedMetadata, BaseKubeObjectCondition } from "../api-types";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "../types/pod-template-spec";
export interface Scale {
apiVersion: "autoscaling/v1";
kind: "Scale";
metadata: KubeObjectMetadata;
spec: {
replicas: number;
};
status: {
replicas: number;
selector: string;
};
}
export interface ReplicationControllerSpec {
/**
* Minimum number of seconds for which a newly created pod should be ready without any of its container crashing,

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeObjectMetadata } from "../api-types";
export interface Scale {
apiVersion: "autoscaling/v1";
kind: "Scale";
metadata: KubeObjectMetadata;
spec: {
replicas: number;
};
status: {
replicas: number;
selector: string;
};
}
export interface ScaleCreateOptions {
apiVersion?: "autoscaling/v1";
kind?: "Scale";
spec: {
replicas: number;
};
}