From 4b1d740d6154d06b655cc106b09b5a39aad2703a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 22 Mar 2023 11:51:26 -0400 Subject: [PATCH] Fix behaviour of auto generated CRD KubeApis and KubeObjectStores (#7384) * Simplify CRD KubeApi registrations - Switch to auto injectable registrations Signed-off-by: Sebastian Malton * Make sure that stores can still be retrieved Signed-off-by: Sebastian Malton * Cleanup get extension fake to simplify impl Signed-off-by: Sebastian Malton * Simplify logic for extensionInjectable Signed-off-by: Sebastian Malton * Fix test in differencing registrator Signed-off-by: Sebastian Malton * Cleanup code style Signed-off-by: Sebastian Malton * Fix some tests Signed-off-by: Sebastian Malton * Fix HPA details tests Signed-off-by: Sebastian Malton --------- Signed-off-by: Sebastian Malton --- .../k8s-api/__tests__/api-manager.test.ts | 90 +++++++++++++++ .../common/k8s-api/api-manager/api-manager.ts | 35 +++++- .../auto-registration-emitter.injectable.ts | 2 - .../k8s-api/api-manager/crd-api-token.ts | 11 ++ ...create-custom-resource-store.injectable.ts | 27 +++++ .../k8s-api/api-manager/manager.injectable.ts | 6 + .../src/common/k8s-api/kube-object.store.ts | 2 +- .../src/common/utils/registrator-helper.ts | 25 +++++ .../extension/extension.injectable.ts | 50 +++------ ...-characters-in-page-registrations.test.tsx | 3 +- ...setup-auto-crd-api-creations.injectable.ts | 53 +++++++++ .../setup-auto-registration.injectable.ts | 52 +-------- .../hpa-details.test.tsx | 16 +++ .../definition.store.injectable.ts | 2 - .../+custom-resources/definition.store.ts | 19 +--- .../test-utils/get-application-builder.tsx | 103 ++++++------------ .../test-utils/get-extension-fake.ts | 8 +- 17 files changed, 315 insertions(+), 189 deletions(-) create mode 100644 packages/core/src/common/k8s-api/api-manager/crd-api-token.ts create mode 100644 packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts create mode 100644 packages/core/src/common/utils/registrator-helper.ts create mode 100644 packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts diff --git a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts index 6ea6327038..48fdb3e544 100644 --- a/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/api-manager.test.ts @@ -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 { 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, + }); + }); + }); + }); + }); }); diff --git a/packages/core/src/common/k8s-api/api-manager/api-manager.ts b/packages/core/src/common/k8s-api/api-manager/api-manager.ts index f6c2758921..6b60ba8e7a 100644 --- a/packages/core/src/common/k8s-api/api-manager/api-manager.ts +++ b/packages/core/src/common/k8s-api/api-manager/api-manager.ts @@ -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 extends KubeObjectStore ? Store @@ -26,13 +27,15 @@ export type FindApiCallback = (api: KubeApi) => boolean; interface Dependencies { readonly apis: IComputedValue; + readonly crdApis: IComputedValue; readonly stores: IComputedValue; + createCustomResourceStore: CreateCustomResourceStore; } export class ApiManager { private readonly externalApis = observable.array(); private readonly externalStores = observable.array(); - + private readonly defaultCrdStores = observable.map(); private readonly apis = observable.map(); 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: RegisterableApi): KubeObjectStoreFrom | 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 { diff --git a/packages/core/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts b/packages/core/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts index d9a68a988c..714e9d8952 100644 --- a/packages/core/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts +++ b/packages/core/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts @@ -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) => void; } diff --git a/packages/core/src/common/k8s-api/api-manager/crd-api-token.ts b/packages/core/src/common/k8s-api/api-manager/crd-api-token.ts new file mode 100644 index 0000000000..d8f0c918c2 --- /dev/null +++ b/packages/core/src/common/k8s-api/api-manager/crd-api-token.ts @@ -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({ + id: "custom-resource-definition-api-token", +}); diff --git a/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts b/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts new file mode 100644 index 0000000000..fbf46e2b42 --- /dev/null +++ b/packages/core/src/common/k8s-api/api-manager/create-custom-resource-store.injectable.ts @@ -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 = (api: KubeApi) => CustomResourceStore; + +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; diff --git a/packages/core/src/common/k8s-api/api-manager/manager.injectable.ts b/packages/core/src/common/k8s-api/api-manager/manager.injectable.ts index f0b61c28b6..2ffd41a296 100644 --- a/packages/core/src/common/k8s-api/api-manager/manager.injectable.ts +++ b/packages/core/src/common/k8s-api/api-manager/manager.injectable.ts @@ -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), }); }, }); diff --git a/packages/core/src/common/k8s-api/kube-object.store.ts b/packages/core/src/common/k8s-api/kube-object.store.ts index 09b26d4142..fa17fd1f2c 100644 --- a/packages/core/src/common/k8s-api/kube-object.store.ts +++ b/packages/core/src/common/k8s-api/kube-object.store.ts @@ -88,7 +88,7 @@ export interface KubeObjectStoreDependencies { readonly logger: Logger; } -export abstract class KubeObjectStore< +export class KubeObjectStore< K extends KubeObject = KubeObject, A extends KubeApi = KubeApi>, D extends KubeJsonApiDataFor = KubeApiDataFrom, diff --git a/packages/core/src/common/utils/registrator-helper.ts b/packages/core/src/common/utils/registrator-helper.ts new file mode 100644 index 0000000000..4a9cc5c2d2 --- /dev/null +++ b/packages/core/src/common/utils/registrator-helper.ts @@ -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[], rawPrevious: Injectable[] = []) => { + 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()); + } +); diff --git a/packages/core/src/extensions/extension-loader/extension/extension.injectable.ts b/packages/core/src/extensions/extension-loader/extension/extension.injectable.ts index 07f054d3fd..d54d997d09 100644 --- a/packages/core/src/extensions/extension-loader/extension/extension.injectable.ts +++ b/packages/core/src/extensions/extension-loader/extension/extension.injectable.ts @@ -2,29 +2,18 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Injectable } from "@ogre-tools/injectable"; import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; -import { difference, find, map } from "lodash"; import { reaction, runInAction } from "mobx"; import { disposer } from "@k8slens/utilities"; import type { LensExtension } from "../../lens-extension"; import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token"; +import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper"; export interface Extension { register: () => void; deregister: () => void; } -const idsToInjectables = (ids: string[], injectables: Injectable[]) => ids.map(id => { - const injectable = find(injectables, { id }); - - if (!injectable) { - throw new Error(`Injectable ${id} not found`); - } - - return injectable; -}); - const extensionInjectable = getInjectable({ id: "extension", @@ -35,36 +24,27 @@ const extensionInjectable = getInjectable({ instantiate: (childDi) => { const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken); const reactionDisposer = disposer(); + const injectableDifferencingRegistrator = injectableDifferencingRegistratorWith(childDi); return { register: () => { - extensionRegistrators.forEach((getInjectablesOfExtension) => { - const injectables = getInjectablesOfExtension(instance); + for (const extensionRegistrator of extensionRegistrators) { + const injectables = extensionRegistrator(instance); - reactionDisposer.push( - // injectables is either an array or a computed array, in which case - // we need to update the registered injectables with a reaction every time they change - reaction( - () => Array.isArray(injectables) ? injectables : injectables.get(), - (currentInjectables, previousInjectables = []) => { - // Register new injectables and deregister removed injectables by id - const currentIds = map(currentInjectables, "id"); - const previousIds = map(previousInjectables, "id"); - const idsToAdd = difference(currentIds, previousIds); - const idsToRemove = previousIds.filter(previousId => !currentIds.includes(previousId)); - - if (idsToRemove.length > 0) { - childDi.deregister(...idsToInjectables(idsToRemove, previousInjectables)); - } - - if (idsToAdd.length > 0) { - childDi.register(...idsToInjectables(idsToAdd, currentInjectables)); - } - }, { + if (Array.isArray(injectables)) { + runInAction(() => { + injectableDifferencingRegistrator(injectables); + }); + } else { + reactionDisposer.push(reaction( + () => injectables.get(), + injectableDifferencingRegistrator, + { fireImmediately: true, }, )); - }); + } + } }, deregister: () => { diff --git a/packages/core/src/features/extension-special-characters-in-page-registrations.test.tsx b/packages/core/src/features/extension-special-characters-in-page-registrations.test.tsx index ac038a5743..b9760eba79 100644 --- a/packages/core/src/features/extension-special-characters-in-page-registrations.test.tsx +++ b/packages/core/src/features/extension-special-characters-in-page-registrations.test.tsx @@ -31,8 +31,7 @@ describe("extension special characters in page registrations", () => { describe("when navigating to route with ID having special characters", () => { beforeEach(() => { - const testExtension = - builder.extensions.get("some-extension-id").applicationWindows.only; + const testExtension = builder.extensions.get("some-extension-id").applicationWindows.only; testExtension.navigate("/some-page-id/"); }); diff --git a/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts new file mode 100644 index 0000000000..c23a34d767 --- /dev/null +++ b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-crd-api-creations.injectable.ts @@ -0,0 +1,53 @@ +/** + * 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 { reaction } from "mobx"; +import { customResourceDefinitionApiInjectionToken } from "../../../common/k8s-api/api-manager/crd-api-token"; +import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; +import { KubeApi } from "../../../common/k8s-api/kube-api"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; +import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; +import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper"; +import customResourceDefinitionStoreInjectable from "../../components/+custom-resources/definition.store.injectable"; +import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens"; + +const setupAutoCrdApiCreationsInjectable = getInjectable({ + id: "setup-auto-crd-api-creations", + instantiate: (di) => ({ + run: () => { + const customResourceDefinitionStore = di.inject(customResourceDefinitionStoreInjectable); + const injectableDifferencingRegistrator = injectableDifferencingRegistratorWith(di); + + reaction( + () => customResourceDefinitionStore.getItems().map(toCrdApiInjectable), + injectableDifferencingRegistrator, + { + fireImmediately: true, + }, + ); + }, + }), + injectionToken: beforeClusterFrameStartsSecondInjectionToken, +}); + +export default setupAutoCrdApiCreationsInjectable; + +const toCrdApiInjectable = (crd: CustomResourceDefinition) => getInjectable({ + id: `default-kube-api-for-custom-resource-definition-${crd.getResourceApiBase()}`, + instantiate: (di) => { + const objectConstructor = class extends KubeObject { + static readonly kind = crd.getResourceKind(); + static readonly namespaced = crd.isNamespaced(); + static readonly apiBase = crd.getResourceApiBase(); + }; + + return new KubeApi({ + logger: di.inject(loggerInjectable), + maybeKubeApi: di.inject(maybeKubeApiInjectable), + }, { objectConstructor }); + }, + injectionToken: customResourceDefinitionApiInjectionToken, +}); diff --git a/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts index dbc77a92b8..78b1f90015 100644 --- a/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts +++ b/packages/core/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts @@ -5,71 +5,22 @@ import { getInjectable } from "@ogre-tools/injectable"; import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; -import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resource.store"; -import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; -import type { KubeApiDependencies } from "../../../common/k8s-api/kube-api"; -import { KubeApi } from "../../../common/k8s-api/kube-api"; -import { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeApi } from "../../../common/k8s-api/kube-api"; import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens"; -import type { KubeObjectStoreDependencies } from "../../../common/k8s-api/kube-object.store"; -import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; -import loggerInjectable from "../../../common/logger.injectable"; -import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable"; const setupAutoRegistrationInjectable = getInjectable({ id: "setup-auto-registration", instantiate: (di) => ({ run: () => { const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); - const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; const beforeApiManagerInitializationApis: KubeApi[] = []; - const kubeApiDependencies: KubeApiDependencies = { - logger: di.inject(loggerInjectable), - maybeKubeApi: di.inject(maybeKubeApiInjectable), - }; - const kubeObjectStoreDependencies: KubeObjectStoreDependencies = { - context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), - logger: di.inject(loggerInjectable), - }; let initialized = false; - const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { - const objectConstructor = class extends KubeObject { - static readonly kind = crd.getResourceKind(); - static readonly namespaced = crd.isNamespaced(); - static readonly apiBase = crd.getResourceApiBase(); - }; - - const api = (() => { - const rawApi = apiManager.getApi(objectConstructor.apiBase); - - if (rawApi) { - return rawApi; - } - - const api = new KubeApi(kubeApiDependencies, { objectConstructor }); - - apiManager.registerApi(api); - - return api; - })(); - - if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(kubeObjectStoreDependencies, api)); - } - }; const autoInitKubeApi = (api: KubeApi) => { apiManager.registerApi(api); }; autoRegistrationEmitter - .on("customResourceDefinition", (crd) => { - if (initialized) { - autoInitCustomResourceStore(crd); - } else { - beforeApiManagerInitializationCrds.push(crd); - } - }) .on("kubeApi", (api) => { if (initialized) { autoInitKubeApi(api); @@ -81,7 +32,6 @@ const setupAutoRegistrationInjectable = getInjectable({ // NOTE: this MUST happen after the event emitter listeners are registered const apiManager = di.inject(apiManagerInjectable); - beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); beforeApiManagerInitializationApis.forEach(autoInitKubeApi); initialized = true; }, diff --git a/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx b/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx index 48c2183055..aa2d2a3b32 100644 --- a/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx +++ b/packages/core/src/renderer/components/+config-horizontal-pod-autoscalers/hpa-details.test.tsx @@ -4,8 +4,13 @@ */ import type { RenderResult } from "@testing-library/react"; import React from "react"; +import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import { Cluster } from "../../../common/cluster/cluster"; import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints"; +import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; +import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; import { HpaDetails } from "./hpa-details"; @@ -41,6 +46,17 @@ describe("", () => { beforeEach(() => { const di = getDiForUnitTesting(); + di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); + di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); + di.override(storesAndApisCanBeCreatedInjectable, () => true); + di.override(hostedClusterInjectable, () => new Cluster({ + contextName: "some-context-name", + id: "some-cluster-id", + kubeConfigPath: "/some-path-to-a-kubeconfig", + }, { + clusterServerUrl: "https://localhost:8080", + })); + render = renderFor(di); }); diff --git a/packages/core/src/renderer/components/+custom-resources/definition.store.injectable.ts b/packages/core/src/renderer/components/+custom-resources/definition.store.injectable.ts index f1d8f635b8..dbbd2460aa 100644 --- a/packages/core/src/renderer/components/+custom-resources/definition.store.injectable.ts +++ b/packages/core/src/renderer/components/+custom-resources/definition.store.injectable.ts @@ -4,7 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; -import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/kube-object-store-token"; import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; import loggerInjectable from "../../../common/logger.injectable"; @@ -20,7 +19,6 @@ const customResourceDefinitionStoreInjectable = getInjectable({ const api = di.inject(customResourceDefinitionApiInjectable); return new CustomResourceDefinitionStore({ - autoRegistration: di.inject(autoRegistrationEmitterInjectable), context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable), logger: di.inject(loggerInjectable), }, api); diff --git a/packages/core/src/renderer/components/+custom-resources/definition.store.ts b/packages/core/src/renderer/components/+custom-resources/definition.store.ts index 310e51709a..78ef3982a9 100644 --- a/packages/core/src/renderer/components/+custom-resources/definition.store.ts +++ b/packages/core/src/renderer/components/+custom-resources/definition.store.ts @@ -3,37 +3,22 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { computed, reaction, makeObservable } from "mobx"; +import { computed, makeObservable } from "mobx"; import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store"; import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api"; import type { KubeObject } from "../../../common/k8s-api/kube-object"; -import type TypedEventEmitter from "typed-emitter"; -import type { LegacyAutoRegistration } from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; import autoBind from "auto-bind"; -export interface CustomResourceDefinitionStoreDependencies extends KubeObjectStoreDependencies { - readonly autoRegistration: TypedEventEmitter; -} - export class CustomResourceDefinitionStore extends KubeObjectStore { constructor( - protected readonly dependencies: CustomResourceDefinitionStoreDependencies, + dependencies: KubeObjectStoreDependencies, api: CustomResourceDefinitionApi, opts?: KubeObjectStoreOptions, ) { super(dependencies, api, opts); makeObservable(this); autoBind(this); - - reaction( - () => this.getItems(), - crds => { - for (const crd of crds) { - this.dependencies.autoRegistration.emit("customResourceDefinition", crd); - } - }, - ); } protected sortItems(items: CustomResourceDefinition[]) { diff --git a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx index 4ae3441e76..731c5ba8c7 100644 --- a/packages/core/src/renderer/components/test-utils/get-application-builder.tsx +++ b/packages/core/src/renderer/components/test-utils/get-application-builder.tsx @@ -53,7 +53,7 @@ import { applicationWindowInjectionToken } from "../../../main/start-main-applic import closeAllWindowsInjectable from "../../../main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable"; import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable"; import type { FakeExtensionOptions } from "./get-extension-fake"; -import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake"; +import { getMainExtensionFakeWith, getRendererExtensionFakeWith } from "./get-extension-fake"; import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable"; import { Namespace } from "../../../common/k8s-api/endpoints"; import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes"; @@ -594,49 +594,28 @@ export const getApplicationBuilder = () => { }, enable: (...extensions) => { - builder.afterWindowStart(({ windowDi }) => { - const rendererExtensionInstances = extensions.map((options) => - getExtensionFakeForRenderer( - windowDi, - options.id, - options.name, - options.rendererOptions || {}, - ), - ); + builder.afterWindowStart(action(({ windowDi }) => { + extensions + .map(getRendererExtensionFakeWith(windowDi)) + .forEach(enableExtensionFor(windowDi, rendererExtensionsStateInjectable)); + })); - rendererExtensionInstances.forEach( - enableExtensionFor(windowDi, rendererExtensionsStateInjectable), - ); - }); - - builder.afterApplicationStart(({ mainDi }) => { - const mainExtensionInstances = extensions.map((extension) => - getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}), - ); - - runInAction(() => { - mainExtensionInstances.forEach( - enableExtensionFor(mainDi, mainExtensionsStateInjectable), - ); - }); - }); + builder.afterApplicationStart(action(({ mainDi }) => { + extensions + .map(getMainExtensionFakeWith(mainDi)) + .forEach(enableExtensionFor(mainDi, mainExtensionsStateInjectable)); + })); }, disable: (...extensions) => { builder.afterWindowStart(({ windowDi }) => { extensions - .map((ext) => ext.id) - .forEach( - disableExtensionFor(windowDi, rendererExtensionsStateInjectable), - ); + .forEach(disableExtensionFor(windowDi, rendererExtensionsStateInjectable)); }); builder.afterApplicationStart(({ mainDi }) => { extensions - .map((ext) => ext.id) - .forEach( - disableExtensionFor(mainDi, mainExtensionsStateInjectable), - ); + .forEach(disableExtensionFor(mainDi, mainExtensionsStateInjectable)); }); }, }, @@ -835,49 +814,29 @@ const selectOptionFor = (builder: ApplicationBuilder, menuId: string) => (labelT userEvent.click(option); }; -const enableExtensionFor = ( - di: DiContainer, - stateInjectable: Injectable, any, any>, -) => { +function enableExtensionFor(di: DiContainer, stateInjectable: Injectable, any, any>) { const extensionState = di.inject(stateInjectable); - const getExtension = (extension: LensExtension) => - di.inject(extensionInjectable, extension); + return (instance: LensExtension) => { + const extension = di.inject(extensionInjectable, instance); - return (extensionInstance: LensExtension) => { - const extension = getExtension(extensionInstance); - - runInAction(() => { - extension.register(); - extensionState.set(extensionInstance.id, extensionInstance); - }); + extension.register(); + extensionState.set(instance.id, instance); }; -}; +} -const disableExtensionFor = - ( - di: DiContainer, - stateInjectable: Injectable, unknown, void>, - ) => - (id: string) => { - const getExtension = (extension: LensExtension) => - di.inject(extensionInjectable, extension); +function disableExtensionFor(di: DiContainer, stateInjectable: Injectable, unknown, void>) { + return (extension: FakeExtensionOptions) => { + const extensionsState = di.inject(stateInjectable); + const instance = extensionsState.get(extension.id); - const extensionsState = di.inject(stateInjectable); + if (!instance) { + throw new Error(`Tried to disable extension with ID "${extension.id}", but it wasn't enabled`); + } - const instance = extensionsState.get(id); + const injectable = di.inject(extensionInjectable, instance); - if (!instance) { - throw new Error( - `Tried to disable extension with ID "${id}", but it wasn't enabled`, - ); - } - - const injectable = getExtension(instance); - - runInAction(() => { - injectable.deregister(); - - extensionsState.delete(id); - }); - }; + injectable.deregister(); + extensionsState.delete(extension.id); + }; +} diff --git a/packages/core/src/renderer/components/test-utils/get-extension-fake.ts b/packages/core/src/renderer/components/test-utils/get-extension-fake.ts index 8079207daa..675b7e93e2 100644 --- a/packages/core/src/renderer/components/test-utils/get-extension-fake.ts +++ b/packages/core/src/renderer/components/test-utils/get-extension-fake.ts @@ -27,7 +27,7 @@ export interface FakeExtensionOptions { mainOptions?: Partial; } -export const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, options: Partial) => { +export const getMainExtensionFakeWith = (di: DiContainer) => ({ id, name, mainOptions = {}}: FakeExtensionOptions) => { const instance = new TestExtensionMain({ id, absolutePath: "irrelevant", @@ -44,7 +44,7 @@ export const getExtensionFakeForMain = (di: DiContainer, id: string, name: strin manifestPath: "irrelevant", }); - Object.assign(instance, options); + Object.assign(instance, mainOptions); (instance as Writable)[lensExtensionDependencies] = { fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), @@ -56,7 +56,7 @@ export const getExtensionFakeForMain = (di: DiContainer, id: string, name: strin return instance; }; -export const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: string, options: Partial) => { +export const getRendererExtensionFakeWith = (di: DiContainer) => ({ id, name, rendererOptions = {}}: FakeExtensionOptions) => { const instance = new TestExtensionRenderer({ id, absolutePath: "irrelevant", @@ -73,7 +73,7 @@ export const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: s manifestPath: "irrelevant", }); - Object.assign(instance, options); + Object.assign(instance, rendererOptions); (instance as Writable)[lensExtensionDependencies] = { categoryRegistry: di.inject(catalogCategoryRegistryInjectable),