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 a1c22a227d..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 @@ -24,6 +24,7 @@ import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-a 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() { @@ -156,6 +157,10 @@ describe("ApiManager", () => { }); }); + 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({ @@ -172,6 +177,34 @@ describe("ApiManager", () => { 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 f73892d0e3..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 @@ -28,12 +29,13 @@ 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) { @@ -117,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; /** @@ -137,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/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 2f2615b471..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 @@ -10,6 +10,7 @@ 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", @@ -27,6 +28,7 @@ const apiManagerInjectable = getInjectable({ 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/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 dfb89669ff..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,59 +5,17 @@ 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); }; @@ -74,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; },