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
+53
@@ -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,
|
||||
});
|
||||
+1
-51
@@ -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;
|
||||
},
|
||||
|
||||
+16
@@ -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("<HpaDetails/>", () => {
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<LegacyAutoRegistration>;
|
||||
}
|
||||
|
||||
export class CustomResourceDefinitionStore extends KubeObjectStore<CustomResourceDefinition, CustomResourceDefinitionApi> {
|
||||
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[]) {
|
||||
|
||||
@@ -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<ObservableMap<string, any>, any, any>,
|
||||
) => {
|
||||
function enableExtensionFor(di: DiContainer, stateInjectable: Injectable<ObservableMap<string, any>, 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<ObservableMap<string, any>, unknown, void>,
|
||||
) =>
|
||||
(id: string) => {
|
||||
const getExtension = (extension: LensExtension) =>
|
||||
di.inject(extensionInjectable, extension);
|
||||
function disableExtensionFor(di: DiContainer, stateInjectable: Injectable<ObservableMap<string, any>, 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);
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export interface FakeExtensionOptions {
|
||||
mainOptions?: Partial<LensMainExtension>;
|
||||
}
|
||||
|
||||
export const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, options: Partial<LensMainExtension>) => {
|
||||
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<LensMainExtension>)[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<LensRendererExtension>) => {
|
||||
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<LensRendererExtension>)[lensExtensionDependencies] = {
|
||||
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||
|
||||
Reference in new issue
Block a user