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 AbortController from "abort-controller";
|
||||||
import { delay } from "../../utils/delay";
|
import { delay } from "../../utils/delay";
|
||||||
import { PassThrough } from "stream";
|
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 {
|
class TestKubeObject extends KubeObject {
|
||||||
static kind = "Pod";
|
static kind = "Pod";
|
||||||
@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject {
|
|||||||
static apiBase = "/api/v1/pods";
|
static apiBase = "/api/v1/pods";
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestKubeApi extends KubeApi<TestKubeObject> { }
|
class TestKubeApi extends KubeApi<TestKubeObject> {
|
||||||
|
public async checkPreferredVersion() {
|
||||||
|
return super.checkPreferredVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("forRemoteCluster", () => {
|
describe("forRemoteCluster", () => {
|
||||||
it("builds api client for KubeObject", async () => {
|
it("builds api client for KubeObject", async () => {
|
||||||
@ -184,6 +194,94 @@ describe("KubeApi", () => {
|
|||||||
expect(kubeApi.apiGroup).toEqual("extensions");
|
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", () => {
|
describe("patch", () => {
|
||||||
let api: TestKubeApi;
|
let api: TestKubeApi;
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export interface IKubeApiLinkRef {
|
|||||||
apiPrefix?: string;
|
apiPrefix?: string;
|
||||||
apiVersion: string;
|
apiVersion: string;
|
||||||
resource: string;
|
resource: string;
|
||||||
name: string;
|
name?: string;
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
|
export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
|
||||||
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
|
const parts = [apiPrefix, apiVersion];
|
||||||
let { namespace } = ref;
|
|
||||||
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
namespace = `namespaces/${namespace}`;
|
parts.push("namespaces", namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [apiPrefix, apiVersion, namespace, resource, name]
|
parts.push(resource);
|
||||||
.filter(v => v)
|
|
||||||
.join("/");
|
if (name) {
|
||||||
|
parts.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("/");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,9 +38,14 @@ import AbortController from "abort-controller";
|
|||||||
import { Agent, AgentOptions } from "https";
|
import { Agent, AgentOptions } from "https";
|
||||||
import type { Patch } from "rfc6902";
|
import type { Patch } from "rfc6902";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The options used for creating a `KubeApi`
|
||||||
|
*/
|
||||||
export interface IKubeApiOptions<T extends KubeObject> {
|
export interface IKubeApiOptions<T extends KubeObject> {
|
||||||
/**
|
/**
|
||||||
* base api-path for listing all resources, e.g. "/api/v1/pods"
|
* 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;
|
apiBase?: string;
|
||||||
|
|
||||||
@ -52,11 +57,33 @@ export interface IKubeApiOptions<T extends KubeObject> {
|
|||||||
*/
|
*/
|
||||||
fallbackApiBases?: string[];
|
fallbackApiBases?: string[];
|
||||||
|
|
||||||
objectConstructor: KubeObjectConstructor<T>;
|
/**
|
||||||
request?: KubeJsonApi;
|
* If `true` then will check all declared apiBases against the kube api server
|
||||||
isNamespaced?: boolean;
|
* for the first accepted one.
|
||||||
kind?: string;
|
*/
|
||||||
checkPreferredVersion?: boolean;
|
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 {
|
export interface IKubeApiQueryParams {
|
||||||
@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor {
|
|||||||
|
|
||||||
export class KubeApi<T extends KubeObject> {
|
export class KubeApi<T extends KubeObject> {
|
||||||
readonly kind: string;
|
readonly kind: string;
|
||||||
readonly apiBase: string;
|
|
||||||
readonly apiPrefix: string;
|
|
||||||
readonly apiGroup: string;
|
|
||||||
readonly apiVersion: string;
|
readonly apiVersion: string;
|
||||||
readonly apiVersionPreferred?: string;
|
apiBase: string;
|
||||||
|
apiPrefix: string;
|
||||||
|
apiGroup: string;
|
||||||
|
apiVersionPreferred?: string;
|
||||||
readonly apiResource: string;
|
readonly apiResource: string;
|
||||||
readonly isNamespaced: boolean;
|
readonly isNamespaced: boolean;
|
||||||
|
|
||||||
@ -264,23 +291,18 @@ export class KubeApi<T extends KubeObject> {
|
|||||||
private watchId = 1;
|
private watchId = 1;
|
||||||
|
|
||||||
constructor(protected options: IKubeApiOptions<T>) {
|
constructor(protected options: IKubeApiOptions<T>) {
|
||||||
const {
|
const { objectConstructor, request, kind, isNamespaced } = options;
|
||||||
objectConstructor,
|
|
||||||
request = apiKube,
|
|
||||||
kind = options.objectConstructor?.kind,
|
|
||||||
isNamespaced = options.objectConstructor?.namespaced,
|
|
||||||
} = options || {};
|
|
||||||
|
|
||||||
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
|
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
|
||||||
|
|
||||||
this.kind = kind;
|
this.options = options;
|
||||||
this.isNamespaced = isNamespaced;
|
this.kind = kind ?? objectConstructor.kind;
|
||||||
|
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
|
||||||
this.apiBase = apiBase;
|
this.apiBase = apiBase;
|
||||||
this.apiPrefix = apiPrefix;
|
this.apiPrefix = apiPrefix;
|
||||||
this.apiGroup = apiGroup;
|
this.apiGroup = apiGroup;
|
||||||
this.apiVersion = apiVersion;
|
this.apiVersion = apiVersion;
|
||||||
this.apiResource = resource;
|
this.apiResource = resource;
|
||||||
this.request = request;
|
this.request = request ?? apiKube;
|
||||||
this.objectConstructor = objectConstructor;
|
this.objectConstructor = objectConstructor;
|
||||||
|
|
||||||
this.parseResponse = this.parseResponse.bind(this);
|
this.parseResponse = this.parseResponse.bind(this);
|
||||||
@ -353,21 +375,16 @@ export class KubeApi<T extends KubeObject> {
|
|||||||
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
|
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
|
||||||
|
|
||||||
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
|
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
|
||||||
Object.defineProperty(this, "apiPrefix", {
|
this.apiPrefix = apiPrefix;
|
||||||
value: apiPrefix,
|
this.apiGroup = apiGroup;
|
||||||
});
|
|
||||||
Object.defineProperty(this, "apiGroup", {
|
|
||||||
value: 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", {
|
this.apiVersionPreferred = res?.preferredVersion?.version ?? null;
|
||||||
value: res?.preferredVersion?.version ?? null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.apiVersionPreferred) {
|
if (this.apiVersionPreferred) {
|
||||||
Object.defineProperty(this, "apiBase", { value: this.getUrl() });
|
this.apiBase = this.computeApiBase();
|
||||||
apiManager.registerApi(this.apiBase, this);
|
apiManager.registerApi(this.apiBase, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,7 +402,15 @@ export class KubeApi<T extends KubeObject> {
|
|||||||
return this.list(params, { limit: 1 });
|
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({
|
const resourcePath = createKubeApiURL({
|
||||||
apiPrefix: this.apiPrefix,
|
apiPrefix: this.apiPrefix,
|
||||||
apiVersion: this.apiVersionWithGroup,
|
apiVersion: this.apiVersionWithGroup,
|
||||||
|
|||||||
@ -57,9 +57,7 @@ export class CrdResources extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get store() {
|
@computed get store() {
|
||||||
if (!this.crd) return null;
|
return apiManager.getStore(this.crd?.getResourceApiBase());
|
||||||
|
|
||||||
return apiManager.getStore(this.crd.getResourceApiBase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store";
|
|||||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
|
||||||
function initStore(crd: CustomResourceDefinition) {
|
function initStore(crd: CustomResourceDefinition) {
|
||||||
const apiBase = crd.getResourceApiBase();
|
const objectConstructor = class extends KubeObject {
|
||||||
const kind = crd.getResourceKind();
|
static readonly kind = crd.getResourceKind();
|
||||||
const isNamespaced = crd.isNamespaced();
|
static readonly namespaced = crd.isNamespaced();
|
||||||
const api = apiManager.getApi(apiBase) ?? new KubeApi({
|
static readonly apiBase = crd.getResourceApiBase();
|
||||||
objectConstructor: KubeObject,
|
};
|
||||||
apiBase,
|
|
||||||
kind,
|
const api = apiManager.getApi(objectConstructor.apiBase)
|
||||||
isNamespaced,
|
?? new KubeApi({ objectConstructor });
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiManager.getStore(api)) {
|
if (!apiManager.getStore(api)) {
|
||||||
apiManager.registerStore(new CRDResourceStore(api));
|
apiManager.registerStore(new CRDResourceStore(api));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user