diff --git a/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts b/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts index ac70727a45..4e312e08f1 100644 --- a/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts +++ b/packages/core/src/renderer/components/+namespaces/namespace-store.test.ts @@ -16,6 +16,7 @@ import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster. import createClusterInjectable from "../../cluster/create-cluster.injectable"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable"; +import removeSubnamespaceInjectable from "./remove-subnamespace.injectable"; import type { NamespaceStore } from "./store"; import namespaceStoreInjectable from "./store.injectable"; @@ -105,9 +106,11 @@ const levelDeepSubChildA = createNamespace("level-deep-subchild-a", { "level-deep-subchild-a.tree.hnc.x-k8s.io/depth": "0", }); -const deleteMock = jest.spyOn(NamespaceApi.prototype, "delete") +const deleteNamespaceMock = jest.spyOn(NamespaceApi.prototype, "delete") .mockImplementation(); +const removeSubnamespaceMock = jest.fn(); + describe("NamespaceStore", () => { let di: DiContainer; let namespaceStore: NamespaceStore; @@ -121,6 +124,7 @@ describe("NamespaceStore", () => { di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(storesAndApisCanBeCreatedInjectable, () => true); + di.override(removeSubnamespaceInjectable, () => removeSubnamespaceMock); const createCluster = di.inject(createClusterInjectable); @@ -223,7 +227,7 @@ describe("NamespaceStore", () => { it("removes namespace", () => { namespaceStore.remove(fullNamespace); - expect(deleteMock).toBeCalledWith({ name: "full-namespace", namespace: undefined }); + expect(deleteNamespaceMock).toBeCalledWith({ name: "full-namespace", namespace: undefined }); }); }); @@ -231,7 +235,13 @@ describe("NamespaceStore", () => { it("does not remove namespace directly", () => { namespaceStore.remove(service1); - expect(deleteMock).not.toBeCalled(); + expect(deleteNamespaceMock).not.toBeCalled(); + }); + + it("calls remove subnamespace function", () => { + namespaceStore.remove(service1); + + expect(removeSubnamespaceMock).toBeCalledWith("service-1"); }); }); }); diff --git a/packages/core/src/renderer/components/+namespaces/remove-subnamespace.injectable.ts b/packages/core/src/renderer/components/+namespaces/remove-subnamespace.injectable.ts new file mode 100644 index 0000000000..34f79fec54 --- /dev/null +++ b/packages/core/src/renderer/components/+namespaces/remove-subnamespace.injectable.ts @@ -0,0 +1,46 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import customResourceDefinitionStoreInjectable from "../+custom-resources/definition.store.injectable"; +import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; +import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable"; +import showSuccessNotificationInjectable from "../notifications/show-success-notification.injectable"; + +const removeSubnamespaceInjectable = getInjectable({ + id: "remove-subnamespace", + + instantiate: (di) => { + const crdStore = di.inject(customResourceDefinitionStoreInjectable); + const apiManager = di.inject(apiManagerInjectable); + const showErrorNotification = di.inject(showErrorNotificationInjectable); + const showSuccessNotification = di.inject(showSuccessNotificationInjectable); + + /** + * Removing subnamespace controlled by hierarchical namespace controller by deleting + * the SubnamespaceAnchor from the parent namespace. Anchor is CRD with the same name + * https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/master/docs/user-guide/quickstart.md#subnamespaces-deep-dive + * @param subnamespaceName namespace name + * @returns + */ + return async (subnamespaceName: string) => { + await crdStore.loadAll(); + + const anchorCrd = crdStore.getByGroup("hnc.x-k8s.io", "subnamespaceanchors"); + const store = apiManager.getStore(anchorCrd?.getResourceApiBase()); + + await store?.loadAll(); + + const anchor = store?.getByName(subnamespaceName); + + if (!anchor) { + showErrorNotification("Error removing namespace: can not find subnamespace anchor."); + + return; + } + + await store?.remove(anchor); + + showSuccessNotification(`Subnamespace ${subnamespaceName} removed`); + } + } +}); + +export default removeSubnamespaceInjectable; \ No newline at end of file diff --git a/packages/core/src/renderer/components/+namespaces/store.injectable.ts b/packages/core/src/renderer/components/+namespaces/store.injectable.ts index 06ae358f9b..cc99beaf9f 100644 --- a/packages/core/src/renderer/components/+namespaces/store.injectable.ts +++ b/packages/core/src/renderer/components/+namespaces/store.injectable.ts @@ -12,7 +12,7 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create import clusterFrameContextForClusterScopedResourcesInjectable from "../../cluster-frame-context/for-cluster-scoped-resources.injectable"; import clusterConfiguredAccessibleNamespacesInjectable from "../../cluster/accessible-namespaces.injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import customResourceDefinitionStoreInjectable from "../+custom-resources/definition.store.injectable"; +import removeSubnamespaceInjectable from "./remove-subnamespace.injectable"; const namespaceStoreInjectable = getInjectable({ id: "namespace-store", @@ -28,7 +28,7 @@ const namespaceStoreInjectable = getInjectable({ storage: createStorage("selected_namespaces", undefined), clusterConfiguredAccessibleNamespaces: di.inject(clusterConfiguredAccessibleNamespacesInjectable), logger: di.inject(loggerInjectable), - crdStore: di.inject(customResourceDefinitionStoreInjectable) + removeSubnamespace: di.inject(removeSubnamespaceInjectable) }, api); }, injectionToken: kubeObjectStoreInjectionToken, diff --git a/packages/core/src/renderer/components/+namespaces/store.ts b/packages/core/src/renderer/components/+namespaces/store.ts index 789b00aeb5..bb01d6b1cc 100644 --- a/packages/core/src/renderer/components/+namespaces/store.ts +++ b/packages/core/src/renderer/components/+namespaces/store.ts @@ -11,7 +11,6 @@ import type { KubeObjectStoreDependencies, KubeObjectStoreLoadingParams } from " import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store"; import type { NamespaceApi } from "../../../common/k8s-api/endpoints/namespace.api"; import { Namespace } from "../../../common/k8s-api/endpoints/namespace.api"; -import type { CustomResourceDefinitionStore } from "../+custom-resources/definition.store"; export interface NamespaceTree { id: string; @@ -22,7 +21,7 @@ export interface NamespaceTree { interface Dependencies extends KubeObjectStoreDependencies { readonly storage: StorageLayer; readonly clusterConfiguredAccessibleNamespaces: IComputedValue; - readonly crdStore: CustomResourceDefinitionStore; + readonly removeSubnamespace: (name: string) => void; } export class NamespaceStore extends KubeObjectStore { @@ -220,22 +219,10 @@ export class NamespaceStore extends KubeObjectStore { }; } - @action - async removeSubnamespace(item: Namespace) { - // Remove anchor from the parent namespace - const crd = this.dependencies.crdStore.getByName(item.getName()); - - if (!crd) { - return; - } - - this.dependencies.crdStore.remove(crd); - } - @action async remove(item: Namespace) { if (item.isSubnamespace()) { - this.removeSubnamespace(item); + this.dependencies.removeSubnamespace(item.getName()); return; }