diff --git a/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts b/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts index 47c39cc0a2..d9a68a988c 100644 --- a/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts +++ b/src/common/k8s-api/api-manager/auto-registration-emitter.injectable.ts @@ -10,7 +10,7 @@ import type { KubeApi } from "../kube-api"; export interface LegacyAutoRegistration { customResourceDefinition: (crd: CustomResourceDefinition) => void; - kubeApi: (api: KubeApi) => void; + kubeApi: (api: KubeApi) => void; } /** diff --git a/src/common/k8s-api/api-manager/auto-registration.injectable.ts b/src/common/k8s-api/api-manager/auto-registration.injectable.ts index 2c3bc1ec7b..0cf1a3055d 100644 --- a/src/common/k8s-api/api-manager/auto-registration.injectable.ts +++ b/src/common/k8s-api/api-manager/auto-registration.injectable.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { CustomResourceDefinition } from "../endpoints"; import { KubeApi } from "../kube-api"; import { KubeObject } from "../kube-object"; import autoRegistrationEmitterInjectable from "./auto-registration-emitter.injectable"; @@ -12,36 +13,61 @@ import { CustomResourceStore } from "./resource.store"; const autoRegistrationInjectable = getInjectable({ id: "api-manager-auto-registration", instantiate: (di) => { - const apiManager = di.inject(apiManagerInjectable); const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); + const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; + const beforeApiManagerInitializationApis: KubeApi[] = []; + 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({ objectConstructor }); + + apiManager.registerApi(api); + + return api; + })(); + + if (!apiManager.getStore(api)) { + apiManager.registerStore(new CustomResourceStore(api)); + } + }; + const autoInitKubeApi = (api: KubeApi) => { + apiManager.registerApi(api); + }; autoRegistrationEmitter .on("customResourceDefinition", (crd) => { - 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({ objectConstructor }); - - apiManager.registerApi(api); - - return api; - })(); - - if (!apiManager.getStore(api)) { - apiManager.registerStore(new CustomResourceStore(api)); + if (initialized) { + autoInitCustomResourceStore(crd); + } else { + beforeApiManagerInitializationCrds.push(crd); } }) - .on("kubeApi", (api) => apiManager.registerApi(api)); + .on("kubeApi", (api) => { + if (initialized) { + autoInitKubeApi(api); + } else { + beforeApiManagerInitializationApis.push(api); + } + }); + + const apiManager = di.inject(apiManagerInjectable); + + beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); + beforeApiManagerInitializationApis.forEach(autoInitKubeApi); + initialized = true; }, }); diff --git a/src/common/k8s-api/api-manager/manager.injectable.ts b/src/common/k8s-api/api-manager/manager.injectable.ts index 5ad94758b0..14e6f20d7f 100644 --- a/src/common/k8s-api/api-manager/manager.injectable.ts +++ b/src/common/k8s-api/api-manager/manager.injectable.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable, getInjectionToken } from "@ogre-tools/injectable"; +import { createStoresAndApisInjectionToken } from "../create-stores-apis.token"; import type { KubeObjectStore } from "../kube-object.store"; import { ApiManager } from "./api-manager"; @@ -14,10 +15,13 @@ const apiManagerInjectable = getInjectable({ id: "api-manager", instantiate: (di) => { const apiManager = new ApiManager(); - const stores = di.injectMany(kubeObjectStoreInjectionToken); - for (const store of stores) { - apiManager.registerStore(store); + if (di.inject(createStoresAndApisInjectionToken)) { + const stores = di.injectMany(kubeObjectStoreInjectionToken); + + for (const store of stores) { + apiManager.registerStore(store); + } } return apiManager; diff --git a/src/common/k8s-api/kube-api.ts b/src/common/k8s-api/kube-api.ts index 9de6366e90..bc7aa3b19a 100644 --- a/src/common/k8s-api/kube-api.ts +++ b/src/common/k8s-api/kube-api.ts @@ -311,7 +311,7 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor { /** * @deprecated In the new extension API, don't expose `KubeApi`'s constructor */ -function legacyRegisterApi(api: KubeApi): void { +function legacyRegisterApi(api: KubeApi): void { // Try both just in case, because we might be in a testing environment for (const env of [Environments.main, Environments.renderer]) { const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(env); @@ -377,7 +377,7 @@ export class KubeApi< this.apiResource = resource; this.request = request; this.objectConstructor = objectConstructor; - legacyRegisterApi(this as unknown as KubeApi); + legacyRegisterApi(this); } get apiVersionWithGroup() { @@ -458,7 +458,7 @@ export class KubeApi< if (this.apiVersionPreferred) { this.apiBase = this.computeApiBase(); - legacyRegisterApi(this as unknown as KubeApi); + legacyRegisterApi(this); } } } diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 52c75c2252..02a3021e0d 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -46,6 +46,7 @@ import assert from "assert"; import openDeleteClusterDialogInjectable from "./components/delete-cluster-dialog/open.injectable"; import { init } from "@sentry/electron/renderer"; import kubernetesClusterCategoryInjectable from "../common/catalog/categories/kubernetes-cluster.injectable"; +import autoRegistrationInjectable from "../common/k8s-api/api-manager/auto-registration.injectable"; configurePackages(); // global packages registerCustomThemes(); // monaco editor themes @@ -78,6 +79,17 @@ export async function bootstrap(di: DiContainer) { const rootNode = createRoot(rootElem); const logPrefix = `[BOOTSTRAP-${process.isMainFrame ? "ROOT" : "CLUSTER"}-FRAME]:`; + /** + * This is injected here to initialize it for the side effect. + * + * The side effect CANNOT be within `apiManagerInjectable` itself since that causes circular + * dependencies with the current need for legacy di use. + * + * This also MUST be done before anything else so that it can start listening for the events for + * auto initialization. + */ + di.inject(autoRegistrationInjectable); + // TODO: Remove temporal dependencies to make timing of initialization not important di.inject(userStoreInjectable); diff --git a/src/renderer/components/kube-object-menu/kube-object-menu.tsx b/src/renderer/components/kube-object-menu/kube-object-menu.tsx index 1639e2bb40..b488bc0937 100644 --- a/src/renderer/components/kube-object-menu/kube-object-menu.tsx +++ b/src/renderer/components/kube-object-menu/kube-object-menu.tsx @@ -103,14 +103,16 @@ class NonInjectedKubeObjectMenu update(object)) : undefined} - removeAction={object && isRemovable ? (() => remove(object)) : undefined} + updateAction={(object && isEditable) ? (() => update(object)) : undefined} + removeAction={(object && isRemovable) ? (() => remove(object)) : undefined} removeConfirmationMessage={renderRemoveMessage} {...menuProps} > diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts index addcaf2f99..c80fa760a5 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.injectable.ts @@ -11,7 +11,6 @@ import hostedClusterInjectable from "../../../../common/cluster-store/hosted-clu import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; import clusterFrameContextInjectable from "../../../cluster-frame-context/cluster-frame-context.injectable"; import assert from "assert"; -import autoRegistrationInjectable from "../../../../common/k8s-api/api-manager/auto-registration.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -21,14 +20,6 @@ const initClusterFrameInjectable = getInjectable({ assert(hostedCluster, "This can only be injected within a cluster frame"); - /** - * This is injected here to initialize it for the side effect. - * - * The side effect CANNOT be within `apiManagerInjectable` itself since that causes circular - * dependencies with the current need for legacy di use - */ - di.inject(autoRegistrationInjectable); - return initClusterFrame({ hostedCluster, loadExtensions: di.inject(extensionLoaderInjectable).loadOnClusterRenderer,