mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix IngressApi being registered with a wrong new apiBase (#4485)
This commit is contained in:
parent
7973f4bce2
commit
304941d397
@ -26,6 +26,12 @@ import { KubeObject } from "../kube-object";
|
||||
import AbortController from "abort-controller";
|
||||
import { delay } from "../../utils/delay";
|
||||
import { PassThrough } from "stream";
|
||||
import { ApiManager, apiManager } from "../api-manager";
|
||||
import { Ingress, Pod } from "../endpoints";
|
||||
|
||||
jest.mock("../api-manager");
|
||||
|
||||
const mockApiManager = apiManager as jest.Mocked<ApiManager>;
|
||||
|
||||
class TestKubeObject extends KubeObject {
|
||||
static kind = "Pod";
|
||||
@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject {
|
||||
static apiBase = "/api/v1/pods";
|
||||
}
|
||||
|
||||
class TestKubeApi extends KubeApi<TestKubeObject> { }
|
||||
class TestKubeApi extends KubeApi<TestKubeObject> {
|
||||
public async checkPreferredVersion() {
|
||||
return super.checkPreferredVersion();
|
||||
}
|
||||
}
|
||||
|
||||
describe("forRemoteCluster", () => {
|
||||
it("builds api client for KubeObject", async () => {
|
||||
@ -184,6 +194,94 @@ describe("KubeApi", () => {
|
||||
expect(kubeApi.apiGroup).toEqual("extensions");
|
||||
});
|
||||
|
||||
describe("checkPreferredVersion", () => {
|
||||
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
const api = new TestKubeApi({
|
||||
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",
|
||||
},
|
||||
};
|
||||
}),
|
||||
} as any,
|
||||
});
|
||||
|
||||
await api.checkPreferredVersion();
|
||||
|
||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
||||
expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything());
|
||||
});
|
||||
|
||||
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
|
||||
expect.hasAssertions();
|
||||
|
||||
const api = new TestKubeApi({
|
||||
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",
|
||||
},
|
||||
};
|
||||
}),
|
||||
} as any,
|
||||
});
|
||||
|
||||
await api.checkPreferredVersion();
|
||||
|
||||
expect(api.apiVersionPreferred).toBe("v1beta1");
|
||||
expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
describe("patch", () => {
|
||||
let api: TestKubeApi;
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ export interface IKubeApiLinkRef {
|
||||
apiPrefix?: string;
|
||||
apiVersion: string;
|
||||
resource: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed {
|
||||
};
|
||||
}
|
||||
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
|
||||
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
|
||||
let { namespace } = ref;
|
||||
export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
|
||||
const parts = [apiPrefix, apiVersion];
|
||||
|
||||
if (namespace) {
|
||||
namespace = `namespaces/${namespace}`;
|
||||
parts.push("namespaces", namespace);
|
||||
}
|
||||
|
||||
return [apiPrefix, apiVersion, namespace, resource, name]
|
||||
.filter(v => v)
|
||||
.join("/");
|
||||
parts.push(resource);
|
||||
|
||||
if (name) {
|
||||
parts.push(name);
|
||||
}
|
||||
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
@ -38,9 +38,14 @@ import AbortController from "abort-controller";
|
||||
import { Agent, AgentOptions } from "https";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
/**
|
||||
* The options used for creating a `KubeApi`
|
||||
*/
|
||||
export interface IKubeApiOptions<T extends KubeObject> {
|
||||
/**
|
||||
* base api-path for listing all resources, e.g. "/api/v1/pods"
|
||||
*
|
||||
* If not specified then will be the one on the `objectConstructor`
|
||||
*/
|
||||
apiBase?: string;
|
||||
|
||||
@ -52,11 +57,33 @@ export interface IKubeApiOptions<T extends KubeObject> {
|
||||
*/
|
||||
fallbackApiBases?: string[];
|
||||
|
||||
objectConstructor: KubeObjectConstructor<T>;
|
||||
request?: KubeJsonApi;
|
||||
isNamespaced?: boolean;
|
||||
kind?: string;
|
||||
/**
|
||||
* If `true` then will check all declared apiBases against the kube api server
|
||||
* for the first accepted one.
|
||||
*/
|
||||
checkPreferredVersion?: boolean;
|
||||
|
||||
/**
|
||||
* The constructor for the kube objects returned from the API
|
||||
*/
|
||||
objectConstructor: KubeObjectConstructor<T>;
|
||||
|
||||
/**
|
||||
* The api instance to use for making requests
|
||||
*
|
||||
* @default apiKube
|
||||
*/
|
||||
request?: KubeJsonApi;
|
||||
|
||||
/**
|
||||
* @deprecated should be specified by `objectConstructor`
|
||||
*/
|
||||
isNamespaced?: boolean;
|
||||
|
||||
/**
|
||||
* @deprecated should be specified by `objectConstructor`
|
||||
*/
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
export interface IKubeApiQueryParams {
|
||||
@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor {
|
||||
|
||||
export class KubeApi<T extends KubeObject> {
|
||||
readonly kind: string;
|
||||
readonly apiBase: string;
|
||||
readonly apiPrefix: string;
|
||||
readonly apiGroup: string;
|
||||
readonly apiVersion: string;
|
||||
readonly apiVersionPreferred?: string;
|
||||
apiBase: string;
|
||||
apiPrefix: string;
|
||||
apiGroup: string;
|
||||
apiVersionPreferred?: string;
|
||||
readonly apiResource: string;
|
||||
readonly isNamespaced: boolean;
|
||||
|
||||
@ -264,23 +291,18 @@ export class KubeApi<T extends KubeObject> {
|
||||
private watchId = 1;
|
||||
|
||||
constructor(protected options: IKubeApiOptions<T>) {
|
||||
const {
|
||||
objectConstructor,
|
||||
request = apiKube,
|
||||
kind = options.objectConstructor?.kind,
|
||||
isNamespaced = options.objectConstructor?.namespaced,
|
||||
} = options || {};
|
||||
|
||||
const { objectConstructor, request, kind, isNamespaced } = options;
|
||||
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
|
||||
|
||||
this.kind = kind;
|
||||
this.isNamespaced = isNamespaced;
|
||||
this.options = options;
|
||||
this.kind = kind ?? objectConstructor.kind;
|
||||
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
|
||||
this.apiBase = apiBase;
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = apiGroup;
|
||||
this.apiVersion = apiVersion;
|
||||
this.apiResource = resource;
|
||||
this.request = request;
|
||||
this.request = request ?? apiKube;
|
||||
this.objectConstructor = objectConstructor;
|
||||
|
||||
this.parseResponse = this.parseResponse.bind(this);
|
||||
@ -353,21 +375,16 @@ export class KubeApi<T extends KubeObject> {
|
||||
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
|
||||
|
||||
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
|
||||
Object.defineProperty(this, "apiPrefix", {
|
||||
value: apiPrefix,
|
||||
});
|
||||
Object.defineProperty(this, "apiGroup", {
|
||||
value: apiGroup,
|
||||
});
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = apiGroup;
|
||||
|
||||
const res = await this.request.get<IKubePreferredVersion>(`${this.apiPrefix}/${this.apiGroup}`);
|
||||
const url = [apiPrefix, apiGroup].filter(Boolean).join("/");
|
||||
const res = await this.request.get<IKubePreferredVersion>(url);
|
||||
|
||||
Object.defineProperty(this, "apiVersionPreferred", {
|
||||
value: res?.preferredVersion?.version ?? null,
|
||||
});
|
||||
this.apiVersionPreferred = res?.preferredVersion?.version ?? null;
|
||||
|
||||
if (this.apiVersionPreferred) {
|
||||
Object.defineProperty(this, "apiBase", { value: this.getUrl() });
|
||||
this.apiBase = this.computeApiBase();
|
||||
apiManager.registerApi(this.apiBase, this);
|
||||
}
|
||||
}
|
||||
@ -385,7 +402,15 @@ export class KubeApi<T extends KubeObject> {
|
||||
return this.list(params, { limit: 1 });
|
||||
}
|
||||
|
||||
getUrl({ name, namespace = "default" }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) {
|
||||
private computeApiBase(): string {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
});
|
||||
}
|
||||
|
||||
getUrl({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) {
|
||||
const resourcePath = createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
|
||||
@ -57,9 +57,7 @@ export class CrdResources extends React.Component<Props> {
|
||||
}
|
||||
|
||||
@computed get store() {
|
||||
if (!this.crd) return null;
|
||||
|
||||
return apiManager.getStore(this.crd.getResourceApiBase());
|
||||
return apiManager.getStore(this.crd?.getResourceApiBase());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
|
||||
function initStore(crd: CustomResourceDefinition) {
|
||||
const apiBase = crd.getResourceApiBase();
|
||||
const kind = crd.getResourceKind();
|
||||
const isNamespaced = crd.isNamespaced();
|
||||
const api = apiManager.getApi(apiBase) ?? new KubeApi({
|
||||
objectConstructor: KubeObject,
|
||||
apiBase,
|
||||
kind,
|
||||
isNamespaced,
|
||||
});
|
||||
const objectConstructor = class extends KubeObject {
|
||||
static readonly kind = crd.getResourceKind();
|
||||
static readonly namespaced = crd.isNamespaced();
|
||||
static readonly apiBase = crd.getResourceApiBase();
|
||||
};
|
||||
|
||||
const api = apiManager.getApi(objectConstructor.apiBase)
|
||||
?? new KubeApi({ objectConstructor });
|
||||
|
||||
if (!apiManager.getStore(api)) {
|
||||
apiManager.registerStore(new CRDResourceStore(api));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user