mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix behaviour of auto generated CRD KubeApis and KubeObjectStores (#7384)
* Simplify CRD KubeApi registrations - Switch to auto injectable registrations Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make sure that stores can still be retrieved Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup get extension fake to simplify impl Signed-off-by: Sebastian Malton <sebastian@malton.name> * Simplify logic for extensionInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test in differencing registrator Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup code style Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix some tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix HPA details tests Signed-off-by: Sebastian Malton <sebastian@malton.name> --------- Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
1 parent
8a80607d85
commit
4b1d740d61
17 files changed
+315
-189
No files matched your search
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
|
||||
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
|
||||
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
|
||||
@@ -21,6 +22,9 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
|
||||
import { Cluster } from "../../cluster/cluster";
|
||||
import { runInAction } from "mobx";
|
||||
import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-api-token";
|
||||
import assert from "assert";
|
||||
|
||||
class TestApi extends KubeApi<KubeObject> {
|
||||
protected async checkPreferredVersion() {
|
||||
@@ -117,4 +121,90 @@ describe("ApiManager", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("given than a CRD has a default KubeApi registered for it", () => {
|
||||
const apiBase = "/apis/aquasecurity.github.io/v1alpha1/vulnerabilityreports";
|
||||
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
di.register(getInjectable({
|
||||
id: `default-kube-api-for-custom-resource-definition-${apiBase}`,
|
||||
instantiate: (di) => {
|
||||
const objectConstructor = class extends KubeObject {
|
||||
static readonly kind = "VulnerabilityReport";
|
||||
static readonly namespaced = true;
|
||||
static readonly apiBase = apiBase;
|
||||
};
|
||||
|
||||
return Object.assign(
|
||||
new KubeApi({
|
||||
logger: di.inject(loggerInjectable),
|
||||
maybeKubeApi: di.inject(maybeKubeApiInjectable),
|
||||
}, { objectConstructor }),
|
||||
{
|
||||
myField: 1,
|
||||
},
|
||||
);
|
||||
},
|
||||
injectionToken: customResourceDefinitionApiInjectionToken,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("can be retrieved from apiManager", () => {
|
||||
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||
myField: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||
});
|
||||
|
||||
describe("given that an extension registers an api with the same apibase", () => {
|
||||
beforeEach(() => {
|
||||
void Object.assign(new ExternalKubeApi({
|
||||
objectConstructor: KubeObject,
|
||||
apiBase,
|
||||
kind: "VulnerabilityReport",
|
||||
}), {
|
||||
myField: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("the extension's instance is retrievable instead from apiManager", () => {
|
||||
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||
myField: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||
});
|
||||
|
||||
describe("given that an extension registers a store for the same apibase", () => {
|
||||
beforeEach(() => {
|
||||
const api = apiManager.getApi(apiBase);
|
||||
|
||||
assert(api);
|
||||
|
||||
apiManager.registerStore(Object.assign(
|
||||
new KubeObjectStore({
|
||||
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
}, api),
|
||||
{
|
||||
someField: 2,
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
it("can gets the custom KubeObjectStore instance instead", () => {
|
||||
expect(apiManager.getStore(apiBase)).toMatchObject({
|
||||
someField: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,8 @@ import { autorun, action, observable } from "mobx";
|
||||
import type { KubeApi } from "../kube-api";
|
||||
import type { KubeObject, ObjectReference } from "../kube-object";
|
||||
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import { getOrInsertWith, iter } from "@k8slens/utilities";
|
||||
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";
|
||||
|
||||
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
|
||||
? Store
|
||||
@@ -26,13 +27,15 @@ export type FindApiCallback = (api: KubeApi<KubeObject>) => boolean;
|
||||
|
||||
interface Dependencies {
|
||||
readonly apis: IComputedValue<KubeApi[]>;
|
||||
readonly crdApis: IComputedValue<KubeApi[]>;
|
||||
readonly stores: IComputedValue<KubeObjectStore[]>;
|
||||
createCustomResourceStore: CreateCustomResourceStore;
|
||||
}
|
||||
|
||||
export class ApiManager {
|
||||
private readonly externalApis = observable.array<KubeApi>();
|
||||
private readonly externalStores = observable.array<KubeObjectStore>();
|
||||
|
||||
private readonly defaultCrdStores = observable.map<string, KubeObjectStore>();
|
||||
private readonly apis = observable.map<string, KubeApi>();
|
||||
|
||||
constructor(private readonly dependencies: Dependencies) {
|
||||
@@ -56,6 +59,12 @@ export class ApiManager {
|
||||
}
|
||||
}
|
||||
|
||||
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||
if (!newState.has(crdApi.apiBase)) {
|
||||
newState.set(crdApi.apiBase, crdApi);
|
||||
}
|
||||
}
|
||||
|
||||
this.apis.replace(newState);
|
||||
});
|
||||
}
|
||||
@@ -110,6 +119,16 @@ export class ApiManager {
|
||||
this.externalStores.push(store);
|
||||
}
|
||||
|
||||
private apiIsDefaultCrdApi(api: KubeApi): boolean {
|
||||
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||
if (crdApi.apiBase === api.apiBase) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getStore(api: string | undefined): KubeObjectStore | undefined;
|
||||
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
|
||||
/**
|
||||
@@ -130,9 +149,19 @@ export class ApiManager {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return iter.chain(this.dependencies.stores.get().values())
|
||||
const defaultResult = iter.chain(this.dependencies.stores.get().values())
|
||||
.concat(this.externalStores.values())
|
||||
.find(store => store.api.apiBase === api.apiBase);
|
||||
|
||||
if (defaultResult) {
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
if (this.apiIsDefaultCrdApi(api)) {
|
||||
return getOrInsertWith(this.defaultCrdStores, api.apiBase, () => this.dependencies.createCustomResourceStore(api));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import EventEmitter from "events";
|
||||
import type TypedEventEmitter from "typed-emitter";
|
||||
import type { CustomResourceDefinition } from "../endpoints";
|
||||
import type { KubeApi } from "../kube-api";
|
||||
|
||||
export interface LegacyAutoRegistration {
|
||||
customResourceDefinition: (crd: CustomResourceDefinition) => void;
|
||||
kubeApi: (api: KubeApi<any, any>) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { KubeApi } from "../kube-api";
|
||||
|
||||
export const customResourceDefinitionApiInjectionToken = getInjectionToken<KubeApi>({
|
||||
id: "custom-resource-definition-api-token",
|
||||
});
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
|
||||
import loggerInjectable from "../../logger.injectable";
|
||||
import type { KubeApi } from "../kube-api";
|
||||
import type { KubeObject } from "../kube-object";
|
||||
import type { KubeObjectStoreDependencies } from "../kube-object.store";
|
||||
import { CustomResourceStore } from "./resource.store";
|
||||
|
||||
export type CreateCustomResourceStore = <K extends KubeObject>(api: KubeApi<K>) => CustomResourceStore<K>;
|
||||
|
||||
const createCustomResourceStoreInjectable = getInjectable({
|
||||
id: "create-custom-resource-store",
|
||||
instantiate: (di): CreateCustomResourceStore => {
|
||||
const deps: KubeObjectStoreDependencies = {
|
||||
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (api) => new CustomResourceStore(deps, api);
|
||||
},
|
||||
});
|
||||
|
||||
export default createCustomResourceStoreInjectable;
|
||||
@@ -9,6 +9,8 @@ import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-f
|
||||
import { kubeObjectStoreInjectionToken } from "./kube-object-store-token";
|
||||
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import { customResourceDefinitionApiInjectionToken } from "./crd-api-token";
|
||||
import createCustomResourceStoreInjectable from "./create-custom-resource-store.injectable";
|
||||
|
||||
const apiManagerInjectable = getInjectable({
|
||||
id: "api-manager",
|
||||
@@ -23,6 +25,10 @@ const apiManagerInjectable = getInjectable({
|
||||
stores: storesAndApisCanBeCreated
|
||||
? computedInjectMany(kubeObjectStoreInjectionToken)
|
||||
: computed(() => []),
|
||||
crdApis: storesAndApisCanBeCreated
|
||||
? computedInjectMany(customResourceDefinitionApiInjectionToken)
|
||||
: computed(() => []),
|
||||
createCustomResourceStore: di.inject(createCustomResourceStoreInjectable),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ export interface KubeObjectStoreDependencies {
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export abstract class KubeObjectStore<
|
||||
export class KubeObjectStore<
|
||||
K extends KubeObject = KubeObject,
|
||||
A extends KubeApi<K, D> = KubeApi<K, KubeJsonApiDataFor<K>>,
|
||||
D extends KubeJsonApiDataFor<K> = KubeApiDataFrom<K, A>,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { iter } from "@k8slens/utilities";
|
||||
import type { DiContainerForInjection, Injectable } from "@ogre-tools/injectable";
|
||||
|
||||
// Register new injectables and deregister removed injectables by id
|
||||
|
||||
export const injectableDifferencingRegistratorWith = (di: DiContainerForInjection) => (
|
||||
(rawCurrent: Injectable<any, any, any>[], rawPrevious: Injectable<any, any, any>[] = []) => {
|
||||
const current = new Map(rawCurrent.map(inj => [inj.id, inj]));
|
||||
const previous = new Map(rawPrevious.map(inj => [inj.id, inj]));
|
||||
const toAdd = iter.chain(current.entries())
|
||||
.filter(([id]) => !previous.has(id))
|
||||
.collect(entries => new Map(entries));
|
||||
const toRemove = iter.chain(previous.entries())
|
||||
.filter(([id]) => !current.has(id))
|
||||
.collect(entries => new Map(entries));
|
||||
|
||||
di.deregister(...toRemove.values());
|
||||
di.register(...toAdd.values());
|
||||
}
|
||||
);
|
||||
Reference in new issue
Block a user