From 900f02fd8cbd900d83c115320e1473a6fb9d7f84 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 31 Oct 2022 05:59:05 -0700 Subject: [PATCH] Remove global version of appEventBus (#6096) * Remove global version of appEventBus Signed-off-by: Sebastian Malton * Introduce a temporary but better shape of ExecFileInjectable error Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- src/common/__tests__/event-bus.test.ts | 27 ----- ...vent-bus.global-override-for-injectable.ts | 8 -- .../app-event-bus/app-event-bus.injectable.ts | 6 +- .../app-event-bus/emit-event.injectable.ts | 15 ++- src/common/app-event-bus/event-bus.ts | 7 +- .../cluster-store/cluster-store.injectable.ts | 2 + src/common/cluster-store/cluster-store.ts | 7 +- src/common/event-emitter.ts | 4 +- src/common/fs/exec-file.injectable.ts | 11 +- .../helm/add-helm-repository-channel.ts | 2 +- .../helm/remove-helm-repository-channel.ts | 2 +- src/common/k8s/resource-stack.ts | 11 +- .../user-store/user-store.injectable.ts | 2 + src/common/user-store/user-store.ts | 2 + src/common/utils/async-result.ts | 6 +- src/extensions/common-api/event-bus.ts | 6 +- src/extensions/npm/extensions/package.json | 2 +- ...current-version-to-analytics.injectable.ts | 6 +- ...process-checking-for-updates.injectable.ts | 4 +- .../quit-and-install-update.injectable.ts | 4 +- .../delete-cluster-dialog.test.tsx | 4 - .../delete-channel-listener.injectable.ts | 17 ++- ...tom-helm-repository-in-preferences.test.ts | 5 +- ...epository-from-list-in-preferences.test.ts | 5 +- ...e-helm-repositories-in-preferences.test.ts | 20 ++-- .../setup-ipc-main-handlers.injectable.ts | 6 + .../setup-ipc-main-handlers.ts | 18 +-- src/main/getDiForUnitTesting.ts | 6 - .../helm/exec-helm/exec-helm.injectable.ts | 22 +--- .../get-helm-env/get-helm-env.injectable.ts | 2 +- .../call-for-helm-manifest.injectable.ts | 2 +- .../add-helm-repository.injectable.ts | 20 +++- ...get-active-helm-repositories.injectable.ts | 6 +- .../remove-helm-repository.injectable.ts | 16 ++- src/main/lens-proxy/lens-proxy.injectable.ts | 4 + src/main/lens-proxy/lens-proxy.ts | 32 +++--- .../create-resource-applier.injectable.ts | 34 ++++++ .../resource-applier.ts | 108 +++++++++--------- src/main/router/router.test.ts | 2 + .../create-resource-route.injectable.ts | 20 ++-- .../patch-resource-route.injectable.ts | 30 ++--- .../local-shell-session/open.injectable.ts | 4 + .../node-shell-session/open.injectable.ts | 4 + src/main/shell-session/shell-session.ts | 10 +- .../create-application-window.injectable.ts | 10 +- .../emit-close-to-event-bus.injectable.ts | 6 +- ...t-service-start-to-event-bus.injectable.ts | 6 +- .../stop-services-and-exit-app.injectable.ts | 6 +- .../components/+add-cluster/add-cluster.tsx | 13 ++- .../components/+catalog/catalog.test.tsx | 21 ++-- src/renderer/components/+catalog/catalog.tsx | 8 +- .../init-cluster-frame.injectable.ts | 4 +- .../init-cluster-frame/init-cluster-frame.ts | 8 +- 53 files changed, 330 insertions(+), 283 deletions(-) delete mode 100644 src/common/__tests__/event-bus.test.ts delete mode 100644 src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts create mode 100644 src/main/resource-applier/create-resource-applier.injectable.ts rename src/main/{ => resource-applier}/resource-applier.ts (55%) diff --git a/src/common/__tests__/event-bus.test.ts b/src/common/__tests__/event-bus.test.ts deleted file mode 100644 index f90381eb44..0000000000 --- a/src/common/__tests__/event-bus.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { AppEvent } from "../app-event-bus/event-bus"; -import { appEventBus } from "../app-event-bus/event-bus"; -import { assert, Console } from "console"; -import { stdout, stderr } from "process"; - -console = new Console(stdout, stderr); - -describe("event bus tests", () => { - describe("emit", () => { - it("emits an event", () => { - let event: AppEvent | undefined; - - appEventBus.addListener((data) => { - event = data; - }); - - appEventBus.emit({ name: "foo", action: "bar" }); - assert(event); - expect(event?.name).toBe("foo"); - }); - }); -}); diff --git a/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts b/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts deleted file mode 100644 index 2cdd21c919..0000000000 --- a/src/common/app-event-bus/app-event-bus.global-override-for-injectable.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getGlobalOverride } from "../test-utils/get-global-override"; -import emitEventInjectable from "./emit-event.injectable"; - -export default getGlobalOverride(emitEventInjectable, () => () => {}); diff --git a/src/common/app-event-bus/app-event-bus.injectable.ts b/src/common/app-event-bus/app-event-bus.injectable.ts index d707ec9fc3..3dee975f7b 100644 --- a/src/common/app-event-bus/app-event-bus.injectable.ts +++ b/src/common/app-event-bus/app-event-bus.injectable.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { appEventBus } from "./event-bus"; +import { EventEmitter } from "../event-emitter"; +import type { AppEvent } from "./event-bus"; const appEventBusInjectable = getInjectable({ id: "app-event-bus", - instantiate: () => appEventBus, - causesSideEffects: true, + instantiate: () => new EventEmitter<[AppEvent]>, decorable: false, }); diff --git a/src/common/app-event-bus/emit-event.injectable.ts b/src/common/app-event-bus/emit-event.injectable.ts index d5aaafe37b..9c9194ceb8 100644 --- a/src/common/app-event-bus/emit-event.injectable.ts +++ b/src/common/app-event-bus/emit-event.injectable.ts @@ -4,11 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import appEventBusInjectable from "./app-event-bus.injectable"; +import type { AppEvent } from "./event-bus"; -const emitEventInjectable = getInjectable({ - id: "emit-event", - instantiate: (di) => di.inject(appEventBusInjectable).emit, +export type EmitAppEvent = (event: AppEvent) => void; + +const emitAppEventInjectable = getInjectable({ + id: "emit-app-event", + instantiate: (di): EmitAppEvent => { + const bus = di.inject(appEventBusInjectable); + + return (event) => bus.emit(event); + }, decorable: false, }); -export default emitEventInjectable; +export default emitAppEventInjectable; diff --git a/src/common/app-event-bus/event-bus.ts b/src/common/app-event-bus/event-bus.ts index 67d23587c7..d121b842f7 100644 --- a/src/common/app-event-bus/event-bus.ts +++ b/src/common/app-event-bus/event-bus.ts @@ -3,13 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { EventEmitter } from "../event-emitter"; - +/** + * Data for telemetry + */ export interface AppEvent { name: string; action: string; destination?: string; params?: Record; } - -export const appEventBus = new EventEmitter<[AppEvent]>(); diff --git a/src/common/cluster-store/cluster-store.injectable.ts b/src/common/cluster-store/cluster-store.injectable.ts index 5a601e1866..3e7cf86c53 100644 --- a/src/common/cluster-store/cluster-store.injectable.ts +++ b/src/common/cluster-store/cluster-store.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ClusterStore } from "./cluster-store"; import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token"; import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; +import emitAppEventInjectable from "../app-event-bus/emit-event.injectable"; const clusterStoreInjectable = getInjectable({ id: "cluster-store", @@ -16,6 +17,7 @@ const clusterStoreInjectable = getInjectable({ return ClusterStore.createInstance({ createCluster: di.inject(createClusterInjectionToken), readClusterConfigSync: di.inject(readClusterConfigSyncInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), }); }, diff --git a/src/common/cluster-store/cluster-store.ts b/src/common/cluster-store/cluster-store.ts index 62b8cda973..7b46460013 100644 --- a/src/common/cluster-store/cluster-store.ts +++ b/src/common/cluster-store/cluster-store.ts @@ -10,7 +10,6 @@ import { BaseStore } from "../base-store"; import { Cluster } from "../cluster/cluster"; import migrations from "../../migrations/cluster-store"; import logger from "../../main/logger"; -import { appEventBus } from "../app-event-bus/event-bus"; import { ipcMainHandle } from "../ipc"; import { disposer, toJS } from "../utils"; import type { ClusterModel, ClusterId, ClusterState } from "../cluster-types"; @@ -18,6 +17,7 @@ import { requestInitialClusterStates } from "../../renderer/ipc"; import { clusterStates } from "../ipc/cluster"; import type { CreateCluster } from "../cluster/create-cluster-injection-token"; import type { ReadClusterConfigSync } from "./read-cluster-config.injectable"; +import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; export interface ClusterStoreModel { clusters?: ClusterModel[]; @@ -26,6 +26,7 @@ export interface ClusterStoreModel { interface Dependencies { createCluster: CreateCluster; readClusterConfigSync: ReadClusterConfigSync; + emitAppEvent: EmitAppEvent; } export class ClusterStore extends BaseStore { @@ -34,7 +35,7 @@ export class ClusterStore extends BaseStore { protected disposer = disposer(); - constructor(private dependencies: Dependencies) { + constructor(private readonly dependencies: Dependencies) { super({ configName: "lens-cluster-store", accessPropertiesByDotNotation: false, // To make dots safe in cluster context names @@ -115,7 +116,7 @@ export class ClusterStore extends BaseStore { } addCluster(clusterOrModel: ClusterModel | Cluster): Cluster { - appEventBus.emit({ name: "cluster", action: "add" }); + this.dependencies.emitAppEvent({ name: "cluster", action: "add" }); const cluster = clusterOrModel instanceof Cluster ? clusterOrModel diff --git a/src/common/event-emitter.ts b/src/common/event-emitter.ts index 4d7f5ca53c..03f8e2754b 100644 --- a/src/common/event-emitter.ts +++ b/src/common/event-emitter.ts @@ -29,7 +29,7 @@ export class EventEmitter { this.listeners.length = 0; } - emit = (...data: D) => { + emit(...data: D) { for (const [callback, { once }] of this.listeners) { if (once) { this.removeListener(callback); @@ -39,5 +39,5 @@ export class EventEmitter { break; } } - }; + } } diff --git a/src/common/fs/exec-file.injectable.ts b/src/common/fs/exec-file.injectable.ts index 8db31c602f..4a462932c0 100644 --- a/src/common/fs/exec-file.injectable.ts +++ b/src/common/fs/exec-file.injectable.ts @@ -3,26 +3,23 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { ExecFileOptions } from "child_process"; +import type { ExecFileException, ExecFileOptions } from "child_process"; import { execFile } from "child_process"; import type { AsyncResult } from "../utils/async-result"; export interface ExecFile { - (filePath: string, args: string[], options: ExecFileOptions): Promise>; + (filePath: string, args: string[], options?: ExecFileOptions): Promise>; } const execFileInjectable = getInjectable({ id: "exec-file", instantiate: (): ExecFile => (filePath, args, options) => new Promise((resolve) => { - execFile(filePath, args, options, (error, stdout, stderr) => { + execFile(filePath, args, options ?? {}, (error, stdout, stderr) => { if (error) { resolve({ callWasSuccessful: false, - error: { - error, - stderr, - }, + error: Object.assign(error, { stderr }), }); } else { resolve({ diff --git a/src/common/helm/add-helm-repository-channel.ts b/src/common/helm/add-helm-repository-channel.ts index 44471fe426..bf5aa19367 100644 --- a/src/common/helm/add-helm-repository-channel.ts +++ b/src/common/helm/add-helm-repository-channel.ts @@ -6,7 +6,7 @@ import type { HelmRepo } from "./helm-repo"; import type { AsyncResult } from "../utils/async-result"; import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; -export type AddHelmRepositoryChannel = RequestChannel>; +export type AddHelmRepositoryChannel = RequestChannel>; export const addHelmRepositoryChannel: AddHelmRepositoryChannel = { id: "add-helm-repository-channel", diff --git a/src/common/helm/remove-helm-repository-channel.ts b/src/common/helm/remove-helm-repository-channel.ts index 7eda160159..4d479d088c 100644 --- a/src/common/helm/remove-helm-repository-channel.ts +++ b/src/common/helm/remove-helm-repository-channel.ts @@ -6,7 +6,7 @@ import type { AsyncResult } from "../utils/async-result"; import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; import type { HelmRepo } from "./helm-repo"; -export type RemoveHelmRepositoryChannel = RequestChannel>; +export type RemoveHelmRepositoryChannel = RequestChannel>; export const removeHelmRepositoryChannel: RemoveHelmRepositoryChannel = { id: "remove-helm-repository-channel", diff --git a/src/common/k8s/resource-stack.ts b/src/common/k8s/resource-stack.ts index 96ecf2e39c..ccb63f71c7 100644 --- a/src/common/k8s/resource-stack.ts +++ b/src/common/k8s/resource-stack.ts @@ -5,7 +5,6 @@ import fse from "fs-extra"; import path from "path"; import hb from "handlebars"; -import { ResourceApplier } from "../../main/resource-applier"; import type { KubernetesCluster } from "../catalog-entities"; import logger from "../../main/logger"; import { app } from "electron"; @@ -14,6 +13,8 @@ import yaml from "js-yaml"; import { requestKubectlApplyAll, requestKubectlDeleteAll } from "../../renderer/ipc"; import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import productNameInjectable from "../vars/product-name.injectable"; +import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import createResourceApplierInjectable from "../../main/resource-applier/create-resource-applier.injectable"; export class ResourceStack { constructor(protected cluster: KubernetesCluster, protected name: string) {} @@ -52,7 +53,9 @@ export class ResourceStack { kubectlArgs = this.appendKubectlArgs(kubectlArgs); if (app) { - return await new ResourceApplier(clusterModel).kubectlApplyAll(resources, kubectlArgs); + const createResourceApplier = asLegacyGlobalFunctionForExtensionApi(createResourceApplierInjectable); + + return await createResourceApplier(clusterModel).kubectlApplyAll(resources, kubectlArgs); } else { const response = await requestKubectlApplyAll(this.cluster.getId(), resources, kubectlArgs); @@ -76,7 +79,9 @@ export class ResourceStack { kubectlArgs = this.appendKubectlArgs(kubectlArgs); if (app) { - return await new ResourceApplier(clusterModel).kubectlDeleteAll(resources, kubectlArgs); + const createResourceApplier = asLegacyGlobalFunctionForExtensionApi(createResourceApplierInjectable); + + return await createResourceApplier(clusterModel).kubectlDeleteAll(resources, kubectlArgs); } else { const response = await requestKubectlDeleteAll(this.cluster.getId(), resources, kubectlArgs); diff --git a/src/common/user-store/user-store.injectable.ts b/src/common/user-store/user-store.injectable.ts index 5b59a47df3..4e01cc50eb 100644 --- a/src/common/user-store/user-store.injectable.ts +++ b/src/common/user-store/user-store.injectable.ts @@ -5,6 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { UserStore } from "./user-store"; import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; +import emitAppEventInjectable from "../app-event-bus/emit-event.injectable"; const userStoreInjectable = getInjectable({ id: "user-store", @@ -14,6 +15,7 @@ const userStoreInjectable = getInjectable({ return UserStore.createInstance({ selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), }); }, diff --git a/src/common/user-store/user-store.ts b/src/common/user-store/user-store.ts index 0a5874ad87..9b80c4dd72 100644 --- a/src/common/user-store/user-store.ts +++ b/src/common/user-store/user-store.ts @@ -11,6 +11,7 @@ import { getOrInsertSet, toggle, toJS, object } from "../../renderer/utils"; import { DESCRIPTORS } from "./preferences-helpers"; import type { UserPreferencesModel, StoreType } from "./preferences-helpers"; import logger from "../../main/logger"; +import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; // TODO: Remove coupling with Feature import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; @@ -23,6 +24,7 @@ export interface UserStoreModel { interface Dependencies { readonly selectedUpdateChannel: SelectedUpdateChannel; + emitAppEvent: EmitAppEvent; } export class UserStore extends BaseStore /* implements UserStoreFlatModel (when strict null is enabled) */ { diff --git a/src/common/utils/async-result.ts b/src/common/utils/async-result.ts index 4e4f37866e..69927f3275 100644 --- a/src/common/utils/async-result.ts +++ b/src/common/utils/async-result.ts @@ -3,5 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ export type AsyncResult = - | { callWasSuccessful: true; response: Response } + | ( + Response extends void + ? { callWasSuccessful: true; response?: undefined } + : { callWasSuccessful: true; response: Response } + ) | { callWasSuccessful: false; error: Error }; diff --git a/src/extensions/common-api/event-bus.ts b/src/extensions/common-api/event-bus.ts index c6d2a1a34b..d95e3f49d4 100644 --- a/src/extensions/common-api/event-bus.ts +++ b/src/extensions/common-api/event-bus.ts @@ -3,5 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -export { appEventBus } from "../../common/app-event-bus/event-bus"; +import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable"; +import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; + export type { AppEvent } from "../../common/app-event-bus/event-bus"; + +export const appEventBus = asLegacyGlobalForExtensionApi(appEventBusInjectable); diff --git a/src/extensions/npm/extensions/package.json b/src/extensions/npm/extensions/package.json index 6ed1bd2f19..cd59286da0 100644 --- a/src/extensions/npm/extensions/package.json +++ b/src/extensions/npm/extensions/package.json @@ -2,7 +2,7 @@ "name": "@k8slens/extensions", "productName": "OpenLens extensions", "description": "OpenLens - Open Source Kubernetes IDE: extensions", - "version": "0.0.1", + "version": "6.0.0", "copyright": "© 2022 OpenLens Authors", "license": "MIT", "main": "dist/src/extensions/extension-api.js", diff --git a/src/features/application-update/main/emit-current-version-to-analytics.injectable.ts b/src/features/application-update/main/emit-current-version-to-analytics.injectable.ts index 5457e18bad..17fdf89944 100644 --- a/src/features/application-update/main/emit-current-version-to-analytics.injectable.ts +++ b/src/features/application-update/main/emit-current-version-to-analytics.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { afterApplicationIsLoadedInjectionToken } from "../../../main/start-main-application/runnable-tokens/after-application-is-loaded-injection-token"; -import emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time"; import buildVersionInjectable from "../../../main/vars/build-version/build-version.injectable"; @@ -12,13 +12,13 @@ const emitCurrentVersionToAnalyticsInjectable = getInjectable({ id: "emit-current-version-to-analytics", instantiate: (di) => { - const emitEvent = di.inject(emitEventInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); const buildVersion = di.inject(buildVersionInjectable); return { id: "emit-current-version-to-analytics", run: () => { - emitEvent({ + emitAppEvent({ name: "app", action: "current-version", diff --git a/src/features/application-update/main/process-checking-for-updates.injectable.ts b/src/features/application-update/main/process-checking-for-updates.injectable.ts index f97eef7941..40fde375f4 100644 --- a/src/features/application-update/main/process-checking-for-updates.injectable.ts +++ b/src/features/application-update/main/process-checking-for-updates.injectable.ts @@ -9,7 +9,7 @@ import discoveredUpdateVersionInjectable from "../common/discovered-update-versi import { runInAction } from "mobx"; import downloadUpdateInjectable from "./download-update/download-update.injectable"; import checkForUpdatesStartingFromChannelInjectable from "./check-for-updates/check-for-updates-starting-from-channel.injectable"; -import emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time"; const processCheckingForUpdatesInjectable = getInjectable({ @@ -21,7 +21,7 @@ const processCheckingForUpdatesInjectable = getInjectable({ const checkingForUpdatesState = di.inject(updatesAreBeingDiscoveredInjectable); const discoveredVersionState = di.inject(discoveredUpdateVersionInjectable); const checkForUpdatesStartingFromChannel = di.inject(checkForUpdatesStartingFromChannelInjectable); - const emitEvent = di.inject(emitEventInjectable); + const emitEvent = di.inject(emitAppEventInjectable); return async (source: string) => { emitEvent({ diff --git a/src/features/application-update/main/quit-and-install-update.injectable.ts b/src/features/application-update/main/quit-and-install-update.injectable.ts index b82ac89206..3c31b0bcc2 100644 --- a/src/features/application-update/main/quit-and-install-update.injectable.ts +++ b/src/features/application-update/main/quit-and-install-update.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import electronQuitAndInstallUpdateInjectable from "../../../main/electron-app/features/electron-quit-and-install-update.injectable"; import { getCurrentDateTime } from "../../../common/utils/date/get-current-date-time"; -import emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import discoveredUpdateVersionInjectable from "../common/discovered-update-version/discovered-update-version.injectable"; const quitAndInstallUpdateInjectable = getInjectable({ @@ -16,7 +16,7 @@ const quitAndInstallUpdateInjectable = getInjectable({ electronQuitAndInstallUpdateInjectable, ); - const emitEvent = di.inject(emitEventInjectable); + const emitEvent = di.inject(emitAppEventInjectable); const discoveredUpdateVersion = di.inject(discoveredUpdateVersionInjectable); return () => { diff --git a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx index a65d19b052..c96f2714e1 100644 --- a/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx +++ b/src/features/cluster/delete-dialog/delete-cluster-dialog.test.tsx @@ -17,7 +17,6 @@ import { type ApplicationBuilder, getApplicationBuilder } from "../../../rendere import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; -import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; @@ -93,9 +92,6 @@ describe("Deleting a cluster", () => { builder.beforeWindowStart((windowDi) => { windowDi.override(storesAndApisCanBeCreatedInjectable, () => true); openDeleteClusterDialog = windowDi.inject(openDeleteClusterDialogInjectable); - - // TODO: remove this line when all global uses of appEventBus are removed - windowDi.permitSideEffects(appEventBusInjectable); }); builder.afterWindowStart(windowDi => { diff --git a/src/features/cluster/delete-dialog/main/delete-channel-listener.injectable.ts b/src/features/cluster/delete-dialog/main/delete-channel-listener.injectable.ts index 9b7617293a..a2ede65ec0 100644 --- a/src/features/cluster/delete-dialog/main/delete-channel-listener.injectable.ts +++ b/src/features/cluster/delete-dialog/main/delete-channel-listener.injectable.ts @@ -2,19 +2,20 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; +import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import clusterFramesInjectable from "../../../../common/cluster-frames.injectable"; import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable"; import deleteFileInjectable from "../../../../common/fs/delete-file.injectable"; import joinPathsInjectable from "../../../../common/path/join-paths.injectable"; +import { noop } from "../../../../common/utils"; import { getRequestChannelListenerInjectable } from "../../../../main/utils/channel/channel-listeners/listener-tokens"; import { deleteClusterChannel } from "../common/delete-channel"; const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectable({ channel: deleteClusterChannel, handler: (di) => { - const appEventBus = di.inject(appEventBusInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); const clusterStore = di.inject(clusterStoreInjectable); const clusterFrames = di.inject(clusterFramesInjectable); const joinPaths = di.inject(joinPathsInjectable); @@ -22,7 +23,7 @@ const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectab const deleteFile = di.inject(deleteFileInjectable); return async (clusterId) => { - appEventBus.emit({ name: "cluster", action: "remove" }); + emitAppEvent({ name: "cluster", action: "remove" }); const cluster = clusterStore.getById(clusterId); @@ -36,14 +37,10 @@ const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectab // Remove from the cluster store as well, this should clear any old settings clusterStore.clusters.delete(cluster.id); - try { - // remove the local storage file - const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`); + // remove the local storage file + const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`); - await deleteFile(localStorageFilePath); - } catch { - // ignore error - } + await deleteFile(localStorageFilePath).catch(noop); }; }, }); diff --git a/src/features/helm-charts/add-custom-helm-repository-in-preferences.test.ts b/src/features/helm-charts/add-custom-helm-repository-in-preferences.test.ts index abc8283226..e7d2b00bbc 100644 --- a/src/features/helm-charts/add-custom-helm-repository-in-preferences.test.ts +++ b/src/features/helm-charts/add-custom-helm-repository-in-preferences.test.ts @@ -185,10 +185,9 @@ describe("add custom helm repository in preferences", () => { beforeEach(async () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("Some error"), + error: Object.assign(new Error("Some error"), { stderr: "", - }, + }), }); }); diff --git a/src/features/helm-charts/add-helm-repository-from-list-in-preferences.test.ts b/src/features/helm-charts/add-helm-repository-from-list-in-preferences.test.ts index c81bcc5208..eab408f337 100644 --- a/src/features/helm-charts/add-helm-repository-from-list-in-preferences.test.ts +++ b/src/features/helm-charts/add-helm-repository-from-list-in-preferences.test.ts @@ -130,10 +130,9 @@ describe("add helm repository from list in preferences", () => { beforeEach(async () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("Some error"), + error: Object.assign(new Error("Some error"), { stderr: "", - }, + }), }); }); diff --git a/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts b/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts index 99336697ff..b3cf629e1d 100644 --- a/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts +++ b/src/features/helm-charts/listing-active-helm-repositories-in-preferences.test.ts @@ -85,10 +85,9 @@ describe("listing active helm repositories in preferences", () => { beforeEach(async () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("some error"), + error: Object.assign(new Error("Some error"), { stderr: "some-error", - }, + }), }); }); @@ -235,10 +234,9 @@ describe("listing active helm repositories in preferences", () => { beforeEach(async () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("Some error"), + error: Object.assign(new Error("Some error"), { stderr: "Some error", - }, + }), }); }); @@ -271,10 +269,9 @@ describe("listing active helm repositories in preferences", () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("no repositories found. You must add one before updating"), + error: Object.assign(new Error("no repositories found. You must add one before updating"), { stderr: "no repositories found. You must add one before updating", - }, + }), }); }); @@ -300,10 +297,9 @@ describe("listing active helm repositories in preferences", () => { beforeEach(async () => { await execFileMock.resolve({ callWasSuccessful: false, - error: { - error: new Error("Some error"), + error: Object.assign(new Error("Some error"), { stderr: "Some error", - }, + }), }); }); diff --git a/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts b/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts index 3dbbbc1ebb..bec1edce33 100644 --- a/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts +++ b/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts @@ -12,6 +12,8 @@ import operatingSystemThemeInjectable from "../../../theme/operating-system-them import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.injectable"; import askUserForFilePathsInjectable from "../../../ipc/ask-user-for-file-paths.injectable"; import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable"; +import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; +import createResourceApplierInjectable from "../../../resource-applier/create-resource-applier.injectable"; const setupIpcMainHandlersInjectable = getInjectable({ id: "setup-ipc-main-handlers", @@ -24,6 +26,8 @@ const setupIpcMainHandlersInjectable = getInjectable({ const clusterStore = di.inject(clusterStoreInjectable); const operatingSystemTheme = di.inject(operatingSystemThemeInjectable); const askUserForFilePaths = di.inject(askUserForFilePathsInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); + const createResourceApplier = di.inject(createResourceApplierInjectable); return { id: "setup-ipc-main-handlers", @@ -37,6 +41,8 @@ const setupIpcMainHandlersInjectable = getInjectable({ clusterStore, operatingSystemTheme, askUserForFilePaths, + emitAppEvent, + createResourceApplier, }); }, }; diff --git a/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts b/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts index 6a7a17475b..5685b5a201 100644 --- a/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts +++ b/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts @@ -8,12 +8,10 @@ import { clusterFrameMap } from "../../../../common/cluster-frames"; import { clusterActivateHandler, clusterSetFrameIdHandler, clusterVisibilityHandler, clusterRefreshHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster"; import type { ClusterId } from "../../../../common/cluster-types"; import { ClusterStore } from "../../../../common/cluster-store/cluster-store"; -import { appEventBus } from "../../../../common/app-event-bus/event-bus"; import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc"; import type { CatalogEntityRegistry } from "../../../catalog"; import { pushCatalogToRenderer } from "../../../catalog-pusher"; import type { ClusterManager } from "../../../cluster/manager"; -import { ResourceApplier } from "../../../resource-applier"; import type { IComputedValue } from "mobx"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window"; import { handleWindowAction, onLocationChange } from "../../../ipc/window"; @@ -25,6 +23,8 @@ import type { ApplicationMenuItemTypes } from "../../../../features/application- import type { Composite } from "../../../../common/utils/composite/get-composite/get-composite"; import { getApplicationMenuTemplate } from "../../../../features/application-menu/main/populate-application-menu.injectable"; import type { MenuItemRoot } from "../../../../features/application-menu/main/application-menu-item-composite.injectable"; +import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.injectable"; +import type { CreateResourceApplier } from "../../../resource-applier/create-resource-applier.injectable"; interface Dependencies { applicationMenuItemComposite: IComputedValue>; @@ -33,6 +33,8 @@ interface Dependencies { clusterStore: ClusterStore; operatingSystemTheme: IComputedValue; askUserForFilePaths: AskUserForFilePaths; + emitAppEvent: EmitAppEvent; + createResourceApplier: CreateResourceApplier; } export const setupIpcMainHandlers = ({ @@ -42,6 +44,8 @@ export const setupIpcMainHandlers = ({ clusterStore, operatingSystemTheme, askUserForFilePaths, + emitAppEvent, + createResourceApplier, }: Dependencies) => { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { return ClusterStore.getInstance() @@ -71,7 +75,7 @@ export const setupIpcMainHandlers = ({ }); ipcMainHandle(clusterDisconnectHandler, (event, clusterId: ClusterId) => { - appEventBus.emit({ name: "cluster", action: "stop" }); + emitAppEvent({ name: "cluster", action: "stop" }); const cluster = ClusterStore.getInstance().getById(clusterId); if (cluster) { @@ -81,11 +85,11 @@ export const setupIpcMainHandlers = ({ }); ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - appEventBus.emit({ name: "cluster", action: "kubectl-apply-all" }); + emitAppEvent({ name: "cluster", action: "kubectl-apply-all" }); const cluster = ClusterStore.getInstance().getById(clusterId); if (cluster) { - const applier = new ResourceApplier(cluster); + const applier = createResourceApplier(cluster); try { const stdout = await applier.kubectlApplyAll(resources, extraArgs); @@ -100,11 +104,11 @@ export const setupIpcMainHandlers = ({ }); ipcMainHandle(clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - appEventBus.emit({ name: "cluster", action: "kubectl-delete-all" }); + emitAppEvent({ name: "cluster", action: "kubectl-delete-all" }); const cluster = ClusterStore.getInstance().getById(clusterId); if (cluster) { - const applier = new ResourceApplier(cluster); + const applier = createResourceApplier(cluster); try { const stdout = await applier.kubectlDeleteAll(resources, extraArgs); diff --git a/src/main/getDiForUnitTesting.ts b/src/main/getDiForUnitTesting.ts index 8bff8a17db..2be5018b84 100644 --- a/src/main/getDiForUnitTesting.ts +++ b/src/main/getDiForUnitTesting.ts @@ -19,9 +19,6 @@ import type { FileSystemProvisionerStore } from "../extensions/extension-loader/ import userStoreInjectable from "../common/user-store/user-store.injectable"; import type { UserStore } from "../common/user-store"; import hotbarStoreInjectable from "../common/hotbars/store.injectable"; -import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable"; -import { EventEmitter } from "../common/event-emitter"; -import type { AppEvent } from "../common/app-event-bus/event-bus"; import commandLineArgumentsInjectable from "./utils/command-line-arguments.injectable"; import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable"; import lensResourcesDirInjectable from "../common/vars/lens-resources-dir.injectable"; @@ -167,9 +164,6 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {}) execFileInjectable, ]); - // TODO: Remove usages of globally exported appEventBus to get rid of this - di.override(appEventBusInjectable, () => new EventEmitter<[AppEvent]>()); - di.override(broadcastMessageInjectable, () => (channel) => { throw new Error(`Tried to broadcast message to channel "${channel}" over IPC without explicit override.`); }); diff --git a/src/main/helm/exec-helm/exec-helm.injectable.ts b/src/main/helm/exec-helm/exec-helm.injectable.ts index d81da0c8b8..39f45a3f7d 100644 --- a/src/main/helm/exec-helm/exec-helm.injectable.ts +++ b/src/main/helm/exec-helm/exec-helm.injectable.ts @@ -3,11 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; +import type { ExecFileException } from "child_process"; import execFileInjectable from "../../../common/fs/exec-file.injectable"; -import helmBinaryPathInjectable from "../helm-binary-path.injectable"; import type { AsyncResult } from "../../../common/utils/async-result"; +import helmBinaryPathInjectable from "../helm-binary-path.injectable"; -export type ExecHelm = (args: string[]) => Promise>; +export type ExecHelm = (args: string[]) => Promise>; const execHelmInjectable = getInjectable({ id: "exec-helm", @@ -16,20 +17,9 @@ const execHelmInjectable = getInjectable({ const execFile = di.inject(execFileInjectable); const helmBinaryPath = di.inject(helmBinaryPathInjectable); - return async (args) => { - const response = await execFile(helmBinaryPath, args, { - maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB - }); - - if (response.callWasSuccessful) { - return response; - } - - return { - callWasSuccessful: false, - error: response.error.stderr || response.error.error.message, - }; - }; + return async (args) => execFile(helmBinaryPath, args, { + maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB + }); }, }); diff --git a/src/main/helm/get-helm-env/get-helm-env.injectable.ts b/src/main/helm/get-helm-env/get-helm-env.injectable.ts index d6c362a17b..bb74c53f99 100644 --- a/src/main/helm/get-helm-env/get-helm-env.injectable.ts +++ b/src/main/helm/get-helm-env/get-helm-env.injectable.ts @@ -21,7 +21,7 @@ const getHelmEnvInjectable = getInjectable({ const result = await execHelm(["env"]); if (!result.callWasSuccessful) { - return { callWasSuccessful: false, error: result.error }; + return { callWasSuccessful: false, error: result.error.stderr }; } const lines = result.response.split(/\r?\n/); // split by new line feed diff --git a/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts b/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts index f0eefd720c..b5176b25b9 100644 --- a/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts +++ b/src/main/helm/helm-service/get-helm-release-resources/call-for-helm-manifest/call-for-helm-manifest.injectable.ts @@ -30,7 +30,7 @@ const callForHelmManifestInjectable = getInjectable({ ]); if (!result.callWasSuccessful) { - return { callWasSuccessful: false, error: result.error }; + return { callWasSuccessful: false, error: result.error.message }; } return { diff --git a/src/main/helm/repositories/add-helm-repository/add-helm-repository.injectable.ts b/src/main/helm/repositories/add-helm-repository/add-helm-repository.injectable.ts index 73283884c3..5292f8a662 100644 --- a/src/main/helm/repositories/add-helm-repository/add-helm-repository.injectable.ts +++ b/src/main/helm/repositories/add-helm-repository/add-helm-repository.injectable.ts @@ -4,17 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import execHelmInjectable from "../../exec-helm/exec-helm.injectable"; -import type { HelmRepo } from "../../../../common/helm/helm-repo"; import loggerInjectable from "../../../../common/logger.injectable"; +import type { AddHelmRepositoryChannel } from "../../../../common/helm/add-helm-repository-channel"; +import type { RequestChannelHandler } from "../../../utils/channel/channel-listeners/listener-tokens"; const addHelmRepositoryInjectable = getInjectable({ id: "add-helm-repository", - instantiate: (di) => { + instantiate: (di): RequestChannelHandler => { const execHelm = di.inject(execHelmInjectable); const logger = di.inject(loggerInjectable); - return async (repo: HelmRepo) => { + return async (repo) => { const { name, url, @@ -54,7 +55,18 @@ const addHelmRepositoryInjectable = getInjectable({ args.push("--cert-file", certFile); } - return await execHelm(args); + const result = await execHelm(args); + + if (result.callWasSuccessful) { + return { + callWasSuccessful: true, + }; + } + + return { + callWasSuccessful: false, + error: result.error.stderr || result.error.message, + }; }; }, }); diff --git a/src/main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable.ts b/src/main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable.ts index c4fb86451b..bd05b950d7 100644 --- a/src/main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable.ts +++ b/src/main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable.ts @@ -78,10 +78,10 @@ const getActiveHelmRepositoriesInjectable = getInjectable({ const updateResult = await execHelm(["repo", "update"]); if (!updateResult.callWasSuccessful) { - if (!updateResult.error.includes(internalHelmErrorForNoRepositoriesFound)) { + if (!updateResult.error.stderr.includes(internalHelmErrorForNoRepositoriesFound)) { return { callWasSuccessful: false, - error: `Error updating Helm repositories: ${updateResult.error}`, + error: `Error updating Helm repositories: ${updateResult.error.stderr}`, }; } const resultOfAddingDefaultRepository = await execHelm(["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"]); @@ -89,7 +89,7 @@ const getActiveHelmRepositoriesInjectable = getInjectable({ if (!resultOfAddingDefaultRepository.callWasSuccessful) { return { callWasSuccessful: false, - error: `Error when adding default Helm repository: ${resultOfAddingDefaultRepository.error}`, + error: `Error when adding default Helm repository: ${resultOfAddingDefaultRepository.error.stderr}`, }; } } diff --git a/src/main/helm/repositories/remove-helm-repository/remove-helm-repository.injectable.ts b/src/main/helm/repositories/remove-helm-repository/remove-helm-repository.injectable.ts index c858667f6a..a662603a96 100644 --- a/src/main/helm/repositories/remove-helm-repository/remove-helm-repository.injectable.ts +++ b/src/main/helm/repositories/remove-helm-repository/remove-helm-repository.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import execHelmInjectable from "../../exec-helm/exec-helm.injectable"; import type { HelmRepo } from "../../../../common/helm/helm-repo"; import loggerInjectable from "../../../../common/logger.injectable"; +import type { AsyncResult } from "../../../../common/utils/async-result"; const removeHelmRepositoryInjectable = getInjectable({ id: "remove-helm-repository", @@ -14,14 +15,25 @@ const removeHelmRepositoryInjectable = getInjectable({ const execHelm = di.inject(execHelmInjectable); const logger = di.inject(loggerInjectable); - return async (repo: HelmRepo) => { + return async (repo: HelmRepo): Promise> => { logger.info(`[HELM]: removing repo ${repo.name} (${repo.url})`); - return execHelm([ + const result = await execHelm([ "repo", "remove", repo.name, ]); + + if (result.callWasSuccessful) { + return { + callWasSuccessful: true, + }; + } + + return { + callWasSuccessful: false, + error: result.error.stderr, + }; }; }, }); diff --git a/src/main/lens-proxy/lens-proxy.injectable.ts b/src/main/lens-proxy/lens-proxy.injectable.ts index 45d893514d..1c6444ccaa 100644 --- a/src/main/lens-proxy/lens-proxy.injectable.ts +++ b/src/main/lens-proxy/lens-proxy.injectable.ts @@ -11,6 +11,8 @@ import clusterManagerInjectable from "../cluster/manager.injectable"; import shellApiRequestInjectable from "./proxy-functions/shell-api-request/shell-api-request.injectable"; import lensProxyPortInjectable from "./lens-proxy-port.injectable"; import contentSecurityPolicyInjectable from "../../common/vars/content-security-policy.injectable"; +import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; +import loggerInjectable from "../../common/logger.injectable"; const lensProxyInjectable = getInjectable({ id: "lens-proxy", @@ -23,6 +25,8 @@ const lensProxyInjectable = getInjectable({ getClusterForRequest: di.inject(clusterManagerInjectable).getClusterForRequest, lensProxyPort: di.inject(lensProxyPortInjectable), contentSecurityPolicy: di.inject(contentSecurityPolicyInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), + logger: di.inject(loggerInjectable), }), }); diff --git a/src/main/lens-proxy/lens-proxy.ts b/src/main/lens-proxy/lens-proxy.ts index e160a49465..d96c9b4919 100644 --- a/src/main/lens-proxy/lens-proxy.ts +++ b/src/main/lens-proxy/lens-proxy.ts @@ -10,13 +10,13 @@ import type httpProxy from "http-proxy"; import { apiPrefix, apiKubePrefix } from "../../common/vars"; import type { Router } from "../router/router"; import type { ClusterContextHandler } from "../context-handler/context-handler"; -import logger from "../logger"; import type { Cluster } from "../../common/cluster/cluster"; import type { ProxyApiRequestArgs } from "./proxy-functions"; -import { appEventBus } from "../../common/app-event-bus/event-bus"; import { getBoolean } from "../utils/parse-query"; import assert from "assert"; import type { SetRequired } from "type-fest"; +import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable"; +import type { Logger } from "../../common/logger"; type GetClusterForRequest = (req: http.IncomingMessage) => Cluster | undefined; @@ -26,10 +26,12 @@ interface Dependencies { getClusterForRequest: GetClusterForRequest; shellApiRequest: (args: ProxyApiRequestArgs) => void | Promise; kubeApiUpgradeRequest: (args: ProxyApiRequestArgs) => void | Promise; + emitAppEvent: EmitAppEvent; readonly router: Router; readonly proxy: httpProxy; readonly lensProxyPort: { set: (portNumber: number) => void }; readonly contentSecurityPolicy: string; + readonly logger: Logger; } const watchParam = "watch"; @@ -81,14 +83,14 @@ export class LensProxy { const cluster = dependencies.getClusterForRequest(req); if (!cluster) { - logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`); + this.dependencies.logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`); socket.destroy(); } else { const isInternal = req.url.startsWith(`${apiPrefix}?`); const reqHandler = isInternal ? dependencies.shellApiRequest : dependencies.kubeApiUpgradeRequest; (async () => reqHandler({ req, socket, head, cluster }))() - .catch(error => logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error)); + .catch(error => this.dependencies.logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error)); } }); } @@ -111,17 +113,17 @@ export class LensProxy { this.dependencies.lensProxyPort.set(port); - logger.info(`[LENS-PROXY]: Proxy server has started at ${address}:${port}`); + this.dependencies.logger.info(`[LENS-PROXY]: Proxy server has started at ${address}:${port}`); this.proxyServer.on("error", (error) => { - logger.info(`[LENS-PROXY]: Subsequent error: ${error}`); + this.dependencies.logger.info(`[LENS-PROXY]: Subsequent error: ${error}`); }); - appEventBus.emit({ name: "lens-proxy", action: "listen", params: { port }}); + this.dependencies.emitAppEvent({ name: "lens-proxy", action: "listen", params: { port }}); resolve(port); }) .once("error", (error) => { - logger.info(`[LENS-PROXY]: Proxy server failed to start: ${error}`); + this.dependencies.logger.info(`[LENS-PROXY]: Proxy server failed to start: ${error}`); reject(error); }); }); @@ -144,7 +146,7 @@ export class LensProxy { return; } - logger.warn(`[LENS-PROXY]: Proxy server has with port known to be considered unsafe to connect to by chrome, restarting...`); + this.dependencies.logger.warn(`[LENS-PROXY]: Proxy server has with port known to be considered unsafe to connect to by chrome, restarting...`); if (seenPorts.has(port)) { /** @@ -160,7 +162,7 @@ export class LensProxy { } close() { - logger.info("[LENS-PROXY]: Closing server"); + this.dependencies.logger.info("[LENS-PROXY]: Closing server"); this.proxyServer.close(); this.closed = true; } @@ -183,10 +185,10 @@ export class LensProxy { return; } - logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${error}`, { url: req.url }); + this.dependencies.logger.error(`[LENS-PROXY]: http proxy errored for cluster: ${error}`, { url: req.url }); if (target) { - logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`); + this.dependencies.logger.debug(`Failed proxy to target: ${JSON.stringify(target, null, 2)}`); if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) { const reqId = this.getRequestId(req); @@ -194,11 +196,11 @@ export class LensProxy { const timeoutMs = retryCount * 250; if (retryCount < 20) { - logger.debug(`Retrying proxy request to url: ${reqId}`); + this.dependencies.logger.debug(`Retrying proxy request to url: ${reqId}`); setTimeout(() => { this.retryCounters.set(reqId, retryCount + 1); this.handleRequest(req as ServerIncomingMessage, res) - .catch(error => logger.error(`[LENS-PROXY]: failed to handle request on proxy error: ${error}`)); + .catch(error => this.dependencies.logger.error(`[LENS-PROXY]: failed to handle request on proxy error: ${error}`)); }, timeoutMs); } } @@ -207,7 +209,7 @@ export class LensProxy { try { res.writeHead(500).end(`Oops, something went wrong.\n${error}`); } catch (e) { - logger.error(`[LENS-PROXY]: Failed to write headers: `, e); + this.dependencies.logger.error(`[LENS-PROXY]: Failed to write headers: `, e); } }); diff --git a/src/main/resource-applier/create-resource-applier.injectable.ts b/src/main/resource-applier/create-resource-applier.injectable.ts new file mode 100644 index 0000000000..04e69d29d4 --- /dev/null +++ b/src/main/resource-applier/create-resource-applier.injectable.ts @@ -0,0 +1,34 @@ +/** + * 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 emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; +import type { Cluster } from "../../common/cluster/cluster"; +import deleteFileInjectable from "../../common/fs/delete-file.injectable"; +import execFileInjectable from "../../common/fs/exec-file.injectable"; +import writeFileInjectable from "../../common/fs/write-file.injectable"; +import loggerInjectable from "../../common/logger.injectable"; +import joinPathsInjectable from "../../common/path/join-paths.injectable"; +import type { ResourceApplierDependencies } from "./resource-applier"; +import { ResourceApplier } from "./resource-applier"; + +export type CreateResourceApplier = (cluster: Cluster) => ResourceApplier; + +const createResourceApplierInjectable = getInjectable({ + id: "create-resource-applier", + instantiate: (di): CreateResourceApplier => { + const deps: ResourceApplierDependencies = { + deleteFile: di.inject(deleteFileInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), + execFile: di.inject(execFileInjectable), + joinPaths: di.inject(joinPathsInjectable), + logger: di.inject(loggerInjectable), + writeFile: di.inject(writeFileInjectable), + }; + + return (cluster) => new ResourceApplier(deps, cluster); + }, +}); + +export default createResourceApplierInjectable; diff --git a/src/main/resource-applier.ts b/src/main/resource-applier/resource-applier.ts similarity index 55% rename from src/main/resource-applier.ts rename to src/main/resource-applier/resource-applier.ts index 4ea984db0a..78d13874b8 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier/resource-applier.ts @@ -3,21 +3,29 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { Cluster } from "../common/cluster/cluster"; -import { exec } from "child_process"; -import fs from "fs-extra"; +import type { Cluster } from "../../common/cluster/cluster"; import * as yaml from "js-yaml"; -import path from "path"; import tempy from "tempy"; -import logger from "./logger"; -import { appEventBus } from "../common/app-event-bus/event-bus"; -import { isChildProcessError } from "../common/utils"; import type { Patch } from "rfc6902"; -import { promiseExecFile } from "../common/utils/promise-exec"; import type { KubernetesObject } from "@kubernetes/client-node"; +import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable"; +import type { Logger } from "../../common/logger"; +import type { WriteFile } from "../../common/fs/write-file.injectable"; +import type { DeleteFile } from "../../common/fs/delete-file.injectable"; +import type { ExecFile } from "../../common/fs/exec-file.injectable"; +import type { JoinPaths } from "../../common/path/join-paths.injectable"; + +export interface ResourceApplierDependencies { + emitAppEvent: EmitAppEvent; + writeFile: WriteFile; + deleteFile: DeleteFile; + execFile: ExecFile; + joinPaths: JoinPaths; + readonly logger: Logger; +} export class ResourceApplier { - constructor(protected cluster: Cluster) {} + constructor(protected readonly dependencies: ResourceApplierDependencies, protected readonly cluster: Cluster) {} /** * Patch a kube resource's manifest, throwing any error that occurs. @@ -27,7 +35,7 @@ export class ResourceApplier { * @param ns The optional namespace of the kube resource */ async patch(name: string, kind: string, patch: Patch, ns?: string): Promise { - appEventBus.emit({ name: "resource", action: "patch" }); + this.dependencies.emitAppEvent({ name: "resource", action: "patch" }); const kubectl = await this.cluster.ensureKubectl(); const kubectlPath = await kubectl.getPath(); @@ -49,23 +57,17 @@ export class ResourceApplier { "-o", "json", ); - try { - const { stdout } = await promiseExecFile(kubectlPath, args); + const result = await this.dependencies.execFile(kubectlPath, args); - return stdout; - } catch (error) { - if (isChildProcessError(error)) { - throw error.stderr ?? error; - } - - throw error; + if (result.callWasSuccessful) { + return result.response; } + + throw result.error.stderr || result.error.message; } async create(resource: string): Promise { - appEventBus.emit({ name: "resource", action: "apply" }); - - console.log({ resource }); + this.dependencies.emitAppEvent({ name: "resource", action: "apply" }); return this.kubectlApply(this.sanitizeObject(resource)); } @@ -82,7 +84,7 @@ export class ResourceApplier { "-f", fileName, ]; - logger.debug(`shooting manifests with ${kubectlPath}`, { args }); + this.dependencies.logger.debug(`shooting manifests with ${kubectlPath}`, { args }); const execEnv = { ...process.env }; const httpsProxy = this.cluster.preferences?.httpsProxy; @@ -92,18 +94,17 @@ export class ResourceApplier { } try { - await fs.writeFile(fileName, content); - const { stdout } = await promiseExecFile(kubectlPath, args); + await this.dependencies.writeFile(fileName, content); - return stdout; - } catch (error) { - if (isChildProcessError(error)) { - throw error.stderr ?? error; + const result = await this.dependencies.execFile(kubectlPath, args); + + if (result.callWasSuccessful) { + return result.response; } - throw error; + throw result.error.stderr || result.error.message; } finally { - await fs.unlink(fileName); + await this.dependencies.deleteFile(fileName); } } @@ -115,39 +116,36 @@ export class ResourceApplier { return this.kubectlCmdAll("delete", resources, extraArgs); } - protected async kubectlCmdAll(subCmd: string, resources: string[], args: string[] = []): Promise { + protected async kubectlCmdAll(subCmd: string, resources: string[], parentArgs: string[] = []): Promise { const kubectl = await this.cluster.ensureKubectl(); const kubectlPath = await kubectl.getPath(); const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath(); + const tmpDir = tempy.directory(); - return new Promise((resolve, reject) => { - const tmpDir = tempy.directory(); + await Promise.all(resources.map((resource, index) => this.dependencies.writeFile( + this.dependencies.joinPaths(tmpDir, `${index}.yaml`), + resource, + ))); - // Dump each resource into tmpDir - resources.forEach((resource, index) => { - fs.writeFileSync(path.join(tmpDir, `${index}.yaml`), resource); - }); - args.push("-f", `"${tmpDir}"`); - const cmd = `"${kubectlPath}" ${subCmd} --kubeconfig "${proxyKubeconfigPath}" ${args.join(" ")}`; + const args = [ + subCmd, + "--kubeconfig", `"${proxyKubeconfigPath}"`, + ...parentArgs, + "-f", `"${tmpDir}"`, + ]; - logger.info(`[RESOURCE-APPLIER] running cmd ${cmd}`); - exec(cmd, (error, stdout) => { - if (error) { - logger.error(`[RESOURCE-APPLIER] cmd errored: ${error}`); - const splitError = error.toString().split(`.yaml": `); + this.dependencies.logger.info(`[RESOURCE-APPLIER] running kubectl`, { args }); + const result = await this.dependencies.execFile(kubectlPath, args); - if (splitError[1]) { - reject(splitError[1]); - } else { - reject(error); - } + if (result.callWasSuccessful) { + return result.response; + } - return; - } + this.dependencies.logger.error(`[RESOURCE-APPLIER] kubectl errored: ${result.error.message}`); - resolve(stdout); - }); - }); + const splitError = result.error.stderr.split(`.yaml": `); + + throw splitError[1] || result.error.message; } protected sanitizeObject(resource: string) { diff --git a/src/main/router/router.test.ts b/src/main/router/router.test.ts index a487104c72..a557eaa6c1 100644 --- a/src/main/router/router.test.ts +++ b/src/main/router/router.test.ts @@ -20,6 +20,7 @@ import type { SetRequired } from "type-fest"; import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; import kubectlBinaryNameInjectable from "../kubectl/binary-name.injectable"; import kubectlDownloadingNormalizedArchInjectable from "../kubectl/normalized-arch.injectable"; +import fsInjectable from "../../common/fs/fs.injectable"; import { runInAction } from "mobx"; describe("router", () => { @@ -32,6 +33,7 @@ describe("router", () => { const di = getDiForUnitTesting({ doGeneralOverrides: true }); mockFs(); + di.permitSideEffects(fsInjectable); di.override(parseRequestInjectable, () => () => Promise.resolve({ payload: "some-payload", diff --git a/src/main/routes/resource-applier/create-resource-route.injectable.ts b/src/main/routes/resource-applier/create-resource-route.injectable.ts index 6e756c1861..3b68201e9d 100644 --- a/src/main/routes/resource-applier/create-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/create-resource-route.injectable.ts @@ -4,20 +4,24 @@ */ import { getRouteInjectable } from "../../router/router.injectable"; import { apiPrefix } from "../../../common/vars"; -import { ResourceApplier } from "../../resource-applier"; import { payloadValidatedClusterRoute } from "../../router/route"; import Joi from "joi"; +import createResourceApplierInjectable from "../../resource-applier/create-resource-applier.injectable"; const createResourceRouteInjectable = getRouteInjectable({ id: "create-resource-route", - instantiate: () => payloadValidatedClusterRoute({ - method: "post", - path: `${apiPrefix}/stack`, - payloadValidator: Joi.string(), - })(async ({ cluster, payload }) => ({ - response: await new ResourceApplier(cluster).create(payload), - })), + instantiate: (di) => { + const createResourceApplier = di.inject(createResourceApplierInjectable); + + return payloadValidatedClusterRoute({ + method: "post", + path: `${apiPrefix}/stack`, + payloadValidator: Joi.string(), + })(async ({ cluster, payload }) => ({ + response: await createResourceApplier(cluster).create(payload), + })); + }, }); export default createResourceRouteInjectable; diff --git a/src/main/routes/resource-applier/patch-resource-route.injectable.ts b/src/main/routes/resource-applier/patch-resource-route.injectable.ts index 53506d6bb0..e962c18607 100644 --- a/src/main/routes/resource-applier/patch-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/patch-resource-route.injectable.ts @@ -4,10 +4,10 @@ */ import { getRouteInjectable } from "../../router/router.injectable"; import { apiPrefix } from "../../../common/vars"; -import { ResourceApplier } from "../../resource-applier"; import { payloadValidatedClusterRoute } from "../../router/route"; import Joi from "joi"; import type { Patch } from "rfc6902"; +import createResourceApplierInjectable from "../../resource-applier/create-resource-applier.injectable"; interface PatchResourcePayload { name: string; @@ -40,18 +40,22 @@ const patchResourcePayloadValidator = Joi.object payloadValidatedClusterRoute({ - method: "patch", - path: `${apiPrefix}/stack`, - payloadValidator: patchResourcePayloadValidator, - })(async ({ cluster, payload }) => ({ - response: await new ResourceApplier(cluster).patch( - payload.name, - payload.kind, - payload.patch, - payload.ns, - ), - })), + instantiate: (di) => { + const createResourceApplier = di.inject(createResourceApplierInjectable); + + return payloadValidatedClusterRoute({ + method: "patch", + path: `${apiPrefix}/stack`, + payloadValidator: patchResourcePayloadValidator, + })(async ({ cluster, payload }) => ({ + response: await createResourceApplier(cluster).patch( + payload.name, + payload.kind, + payload.patch, + payload.ns, + ), + })); + }, }); export default patchResourceRouteInjectable; diff --git a/src/main/shell-session/local-shell-session/open.injectable.ts b/src/main/shell-session/local-shell-session/open.injectable.ts index bfb72911ec..3e9a09405d 100644 --- a/src/main/shell-session/local-shell-session/open.injectable.ts +++ b/src/main/shell-session/local-shell-session/open.injectable.ts @@ -22,6 +22,8 @@ import spawnPtyInjectable from "../spawn-pty.injectable"; import resolvedShellInjectable from "../../../common/user-store/resolved-shell.injectable"; import appNameInjectable from "../../../common/vars/app-name.injectable"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; +import statInjectable from "../../../common/fs/stat/stat.injectable"; export interface OpenLocalShellSessionArgs { websocket: WebSocket; @@ -46,11 +48,13 @@ const openLocalShellSessionInjectable = getInjectable({ appName: di.inject(appNameInjectable), buildVersion: di.inject(buildVersionInjectable), modifyTerminalShellEnv: di.inject(modifyTerminalShellEnvInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), getDirnameOfPath: di.inject(getDirnameOfPathInjectable), joinPaths: di.inject(joinPathsInjectable), getBasenameOfPath: di.inject(getBasenameOfPathInjectable), computeShellEnvironment: di.inject(computeShellEnvironmentInjectable), spawnPty: di.inject(spawnPtyInjectable), + stat: di.inject(statInjectable), }; return (args) => { diff --git a/src/main/shell-session/node-shell-session/open.injectable.ts b/src/main/shell-session/node-shell-session/open.injectable.ts index 6080129d6a..c3095522e1 100644 --- a/src/main/shell-session/node-shell-session/open.injectable.ts +++ b/src/main/shell-session/node-shell-session/open.injectable.ts @@ -17,6 +17,8 @@ import spawnPtyInjectable from "../spawn-pty.injectable"; import resolvedShellInjectable from "../../../common/user-store/resolved-shell.injectable"; import appNameInjectable from "../../../common/vars/app-name.injectable"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; +import statInjectable from "../../../common/fs/stat/stat.injectable"; export interface NodeShellSessionArgs { websocket: WebSocket; @@ -39,6 +41,8 @@ const openNodeShellSessionInjectable = getInjectable({ createKubeJsonApiForCluster: di.inject(createKubeJsonApiForClusterInjectable), computeShellEnvironment: di.inject(computeShellEnvironmentInjectable), spawnPty: di.inject(spawnPtyInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), + stat: di.inject(statInjectable), }; const kubectl = createKubectl(params.cluster.version); const session = new NodeShellSession(dependencies, { kubectl, ...params }); diff --git a/src/main/shell-session/shell-session.ts b/src/main/shell-session/shell-session.ts index e17f533ccb..ef1c3f41aa 100644 --- a/src/main/shell-session/shell-session.ts +++ b/src/main/shell-session/shell-session.ts @@ -10,14 +10,14 @@ import { clearKubeconfigEnvVars } from "../utils/clear-kube-env-vars"; import path from "path"; import os, { userInfo } from "os"; import type * as pty from "node-pty"; -import { appEventBus } from "../../common/app-event-bus/event-bus"; -import { stat } from "fs/promises"; import { getOrInsertWith } from "../../common/utils"; import { type TerminalMessage, TerminalChannels } from "../../common/terminal/channels"; import type { Logger } from "../../common/logger"; import type { ComputeShellEnvironment } from "../utils/shell-env/compute-shell-environment.injectable"; import type { SpawnPty } from "./spawn-pty.injectable"; import type { InitializableState } from "../../common/initializable-state/create"; +import type { EmitAppEvent } from "../../common/app-event-bus/emit-event.injectable"; +import type { Stat } from "../../common/fs/stat/stat.injectable"; export class ShellOpenError extends Error { constructor(message: string, options?: ErrorOptions) { @@ -112,6 +112,8 @@ export interface ShellSessionDependencies { readonly buildVersion: InitializableState; computeShellEnvironment: ComputeShellEnvironment; spawnPty: SpawnPty; + emitAppEvent: EmitAppEvent; + stat: Stat; } export interface ShellSessionArgs { @@ -213,7 +215,7 @@ export abstract class ShellSession { } try { - const stats = await stat(potentialCwd); + const stats = await this.dependencies.stat(potentialCwd); if (stats.isDirectory()) { return potentialCwd; @@ -310,7 +312,7 @@ export abstract class ShellSession { } }); - appEventBus.emit({ name: this.ShellType, action: "open" }); + this.dependencies.emitAppEvent({ name: this.ShellType, action: "open" }); } protected getPathEntries(): string[] { diff --git a/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts b/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts index fcc46ff7a3..703542faaa 100644 --- a/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts +++ b/src/main/start-main-application/lens-window/application-window/create-application-window.injectable.ts @@ -6,9 +6,9 @@ import { getInjectable } from "@ogre-tools/injectable"; import createLensWindowInjectable from "./create-lens-window.injectable"; import lensProxyPortInjectable from "../../../lens-proxy/lens-proxy-port.injectable"; import isMacInjectable from "../../../../common/vars/is-mac.injectable"; -import appEventBusInjectable from "../../../../common/app-event-bus/app-event-bus.injectable"; import waitUntilBundledExtensionsAreLoadedInjectable from "./wait-until-bundled-extensions-are-loaded.injectable"; import { applicationWindowInjectionToken } from "./application-window-injection-token"; +import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import { runInAction } from "mobx"; import appNameInjectable from "../../../../common/vars/app-name.injectable"; @@ -23,9 +23,9 @@ const createApplicationWindowInjectable = getInjectable({ const createLensWindow = di.inject(createLensWindowInjectable); const isMac = di.inject(isMacInjectable); const applicationName = di.inject(appNameInjectable); - const appEventBus = di.inject(appEventBusInjectable); const waitUntilBundledExtensionsAreLoaded = di.inject(waitUntilBundledExtensionsAreLoadedInjectable); const lensProxyPort = di.inject(lensProxyPortInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); return createLensWindow({ id, @@ -40,13 +40,13 @@ const createApplicationWindowInjectable = getInjectable({ titleBarStyle: isMac ? "hiddenInset" : "hidden", centered: false, onFocus: () => { - appEventBus.emit({ name: "app", action: "focus" }); + emitAppEvent({ name: "app", action: "focus" }); }, onBlur: () => { - appEventBus.emit({ name: "app", action: "blur" }); + emitAppEvent({ name: "app", action: "blur" }); }, onDomReady: () => { - appEventBus.emit({ name: "app", action: "dom-ready" }); + emitAppEvent({ name: "app", action: "dom-ready" }); }, onClose: () => { diff --git a/src/main/start-main-application/runnables/emit-close-to-event-bus.injectable.ts b/src/main/start-main-application/runnables/emit-close-to-event-bus.injectable.ts index d671d50823..dc9c04771e 100644 --- a/src/main/start-main-application/runnables/emit-close-to-event-bus.injectable.ts +++ b/src/main/start-main-application/runnables/emit-close-to-event-bus.injectable.ts @@ -3,19 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import { beforeQuitOfFrontEndInjectionToken } from "../runnable-tokens/before-quit-of-front-end-injection-token"; const emitCloseToEventBusInjectable = getInjectable({ id: "emit-close-to-event-bus", instantiate: (di) => { - const appEventBus = di.inject(appEventBusInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); return { id: "emit-close-to-event-bus", run: () => { - appEventBus.emit({ name: "app", action: "close" }); + emitAppEvent({ name: "app", action: "close" }); }, }; }, diff --git a/src/main/start-main-application/runnables/emit-service-start-to-event-bus.injectable.ts b/src/main/start-main-application/runnables/emit-service-start-to-event-bus.injectable.ts index d535b38ad9..9655748a72 100644 --- a/src/main/start-main-application/runnables/emit-service-start-to-event-bus.injectable.ts +++ b/src/main/start-main-application/runnables/emit-service-start-to-event-bus.injectable.ts @@ -3,19 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import { afterApplicationIsLoadedInjectionToken } from "../runnable-tokens/after-application-is-loaded-injection-token"; const emitServiceStartToEventBusInjectable = getInjectable({ id: "emit-service-start-to-event-bus", instantiate: (di) => { - const appEventBus = di.inject(appEventBusInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); return { id: "emit-service-start-to-event-bus", run: () => { - appEventBus.emit({ name: "service", action: "start" }); + emitAppEvent({ name: "service", action: "start" }); }, }; }, diff --git a/src/main/stop-services-and-exit-app.injectable.ts b/src/main/stop-services-and-exit-app.injectable.ts index 9ac505fe65..402862b462 100644 --- a/src/main/stop-services-and-exit-app.injectable.ts +++ b/src/main/stop-services-and-exit-app.injectable.ts @@ -5,9 +5,9 @@ import { getInjectable } from "@ogre-tools/injectable"; import exitAppInjectable from "./electron-app/features/exit-app.injectable"; import clusterManagerInjectable from "./cluster/manager.injectable"; -import appEventBusInjectable from "../common/app-event-bus/app-event-bus.injectable"; import loggerInjectable from "../common/logger.injectable"; import closeAllWindowsInjectable from "./start-main-application/lens-window/hide-all-windows/close-all-windows.injectable"; +import emitAppEventInjectable from "../common/app-event-bus/emit-event.injectable"; const stopServicesAndExitAppInjectable = getInjectable({ id: "stop-services-and-exit-app", @@ -15,12 +15,12 @@ const stopServicesAndExitAppInjectable = getInjectable({ instantiate: (di) => { const exitApp = di.inject(exitAppInjectable); const clusterManager = di.inject(clusterManagerInjectable); - const appEventBus = di.inject(appEventBusInjectable); const logger = di.inject(loggerInjectable); const closeAllWindows = di.inject(closeAllWindowsInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); return () => { - appEventBus.emit({ name: "service", action: "close" }); + emitAppEvent({ name: "service", action: "close" }); closeAllWindows(); clusterManager.stop(); logger.info("SERVICE:QUIT"); diff --git a/src/renderer/components/+add-cluster/add-cluster.tsx b/src/renderer/components/+add-cluster/add-cluster.tsx index 1d062dbd31..5af1e13b48 100644 --- a/src/renderer/components/+add-cluster/add-cluster.tsx +++ b/src/renderer/components/+add-cluster/add-cluster.tsx @@ -12,7 +12,6 @@ import { action, computed, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import React from "react"; import * as uuid from "uuid"; -import { appEventBus } from "../../../common/app-event-bus/event-bus"; import { loadConfigFromString, splitConfig } from "../../../common/kube-helpers"; import { docsUrl } from "../../../common/vars"; import { isDefined, iter } from "../../utils"; @@ -24,6 +23,8 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import getCustomKubeConfigDirectoryInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; +import type { EmitAppEvent } from "../../../common/app-event-bus/emit-event.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import type { GetDirnameOfPath } from "../../../common/path/get-dirname.injectable"; import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable"; @@ -36,6 +37,7 @@ interface Dependencies { getCustomKubeConfigDirectory: (directoryName: string) => string; navigateToCatalog: NavigateToCatalog; getDirnameOfPath: GetDirnameOfPath; + emitAppEvent: EmitAppEvent; } function getContexts(config: KubeConfig): Map { @@ -55,13 +57,13 @@ class NonInjectedAddCluster extends React.Component { @observable isWaiting = false; @observable errors: string[] = []; - constructor(dependencies: Dependencies) { - super(dependencies); + constructor(props: Dependencies) { + super(props); makeObservable(this); } componentDidMount() { - appEventBus.emit({ name: "cluster-add", action: "start" }); + this.props.emitAppEvent({ name: "cluster-add", action: "start" }); } @computed get allErrors(): string[] { @@ -87,7 +89,7 @@ class NonInjectedAddCluster extends React.Component { addClusters = action(async () => { this.isWaiting = true; - appEventBus.emit({ name: "cluster-add", action: "click" }); + this.props.emitAppEvent({ name: "cluster-add", action: "click" }); try { const absPath = this.props.getCustomKubeConfigDirectory(uuid.v4()); @@ -160,5 +162,6 @@ export const AddCluster = withInjectables(NonInjectedAddCluster, { getCustomKubeConfigDirectory: di.inject(getCustomKubeConfigDirectoryInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable), getDirnameOfPath: di.inject(getDirnameOfPathInjectable), + emitAppEvent: di.inject(emitAppEventInjectable), }), }); diff --git a/src/renderer/components/+catalog/catalog.test.tsx b/src/renderer/components/+catalog/catalog.test.tsx index 885fa640d5..6eb6c1ad57 100644 --- a/src/renderer/components/+catalog/catalog.test.tsx +++ b/src/renderer/components/+catalog/catalog.test.tsx @@ -57,7 +57,7 @@ describe("", () => { let di: DiContainer; let catalogEntityStore: CatalogEntityStore; let catalogEntityRegistry: CatalogEntityRegistry; - let emitEvent: (event: AppEvent) => void; + let appEventListener: jest.MockedFunction<(event: AppEvent) => void>; let onRun: jest.MockedFunction<(context: CatalogEntityActionContext) => void | Promise>; let catalogEntityItem: MockCatalogEntity; let render: DiRender; @@ -78,11 +78,8 @@ describe("", () => { catalogEntityItem = createMockCatalogEntity(onRun); catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); - emitEvent = jest.fn(); - - di.override(appEventBusInjectable, () => ({ - emit: emitEvent, - })); + appEventListener = jest.fn(); + di.inject(appEventBusInjectable).addListener(appEventListener); catalogEntityStore = di.inject(catalogEntityStoreInjectable); Object.assign(catalogEntityStore, { @@ -204,24 +201,20 @@ describe("", () => { }); it("emits catalog open AppEvent", () => { - render( - , - ); + render(); - expect(emitEvent).toHaveBeenCalledWith( { + expect(appEventListener).toHaveBeenCalledWith( { action: "open", name: "catalog", }); }); it("emits catalog change AppEvent when changing the category", () => { - render( - , - ); + render(); userEvent.click(screen.getByText("Web Links")); - expect(emitEvent).toHaveBeenLastCalledWith({ + expect(appEventListener).toHaveBeenCalledWith({ action: "change-category", name: "catalog", params: { diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index 723c21da13..2dc7a589fb 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -37,8 +37,6 @@ import type { NavigateToCatalog } from "../../../common/front-end-routing/routes import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import catalogRouteParametersInjectable from "./catalog-route-parameters.injectable"; import { browseCatalogTab } from "./catalog-browse-tab"; -import type { AppEvent } from "../../../common/app-event-bus/event-bus"; -import appEventBusInjectable from "../../../common/app-event-bus/app-event-bus.injectable"; import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { HotbarStore } from "../../../common/hotbars/store"; import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable"; @@ -48,13 +46,15 @@ import type { Navigate } from "../../navigation/navigate.injectable"; import navigateInjectable from "../../navigation/navigate.injectable"; import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable"; import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable"; +import type { EmitAppEvent } from "../../../common/app-event-bus/emit-event.injectable"; +import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; interface Dependencies { catalogPreviousActiveTabStorage: StorageLayer; catalogEntityStore: CatalogEntityStore; getCategoryColumns: (params: GetCategoryColumnsParams) => CategoryColumns; customCategoryViews: IComputedValue>>; - emitEvent: (event: AppEvent) => void; + emitEvent: EmitAppEvent; routeParameters: { group: IComputedValue; kind: IComputedValue; @@ -356,7 +356,7 @@ export const Catalog = withInjectables(NonInjectedCatalog, { customCategoryViews: di.inject(customCategoryViewsInjectable), routeParameters: di.inject(catalogRouteParametersInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable), - emitEvent: di.inject(appEventBusInjectable).emit, + emitEvent: di.inject(emitAppEventInjectable), hotbarStore: di.inject(hotbarStoreInjectable), catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable), visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable), 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 9dd4f59702..93f3f4c7dc 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 @@ -8,9 +8,9 @@ import extensionLoaderInjectable from "../../../../extensions/extension-loader/e import catalogEntityRegistryInjectable from "../../../api/catalog/entity/registry.injectable"; import frameRoutingIdInjectable from "./frame-routing-id/frame-routing-id.injectable"; import hostedClusterInjectable from "../../../cluster-frame-context/hosted-cluster.injectable"; -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 emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; const initClusterFrameInjectable = getInjectable({ id: "init-cluster-frame", @@ -25,7 +25,7 @@ const initClusterFrameInjectable = getInjectable({ loadExtensions: di.inject(extensionLoaderInjectable).loadOnClusterRenderer, catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), frameRoutingId: di.inject(frameRoutingIdInjectable), - emitEvent: di.inject(appEventBusInjectable).emit, + emitAppEvent: di.inject(emitAppEventInjectable), clusterFrameContext: di.inject(clusterFrameContextInjectable), }); }, diff --git a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts index 6d1303e256..6c7dec4b40 100644 --- a/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts +++ b/src/renderer/frames/cluster-frame/init-cluster-frame/init-cluster-frame.ts @@ -7,19 +7,19 @@ import type { CatalogEntityRegistry } from "../../../api/catalog/entity/registry import logger from "../../../../main/logger"; import type { KubernetesCluster } from "../../../../common/catalog-entities"; import { Notifications } from "../../../components/notifications"; -import type { AppEvent } from "../../../../common/app-event-bus/event-bus"; import type { CatalogEntity } from "../../../../common/catalog"; import { when } from "mobx"; import type { ClusterFrameContext } from "../../../cluster-frame-context/cluster-frame-context"; import { KubeObjectStore } from "../../../../common/k8s-api/kube-object.store"; import { requestSetClusterFrameId } from "../../../ipc"; +import type { EmitAppEvent } from "../../../../common/app-event-bus/emit-event.injectable"; interface Dependencies { hostedCluster: Cluster; loadExtensions: (getCluster: () => CatalogEntity) => void; catalogEntityRegistry: CatalogEntityRegistry; frameRoutingId: number; - emitEvent: (event: AppEvent) => void; + emitAppEvent: EmitAppEvent; // TODO: This dependency belongs to KubeObjectStore clusterFrameContext: ClusterFrameContext; @@ -32,7 +32,7 @@ export const initClusterFrame = ({ loadExtensions, catalogEntityRegistry, frameRoutingId, - emitEvent, + emitAppEvent, clusterFrameContext, }: Dependencies) => async (unmountRoot: () => void) => { @@ -70,7 +70,7 @@ export const initClusterFrame = ({ ); setTimeout(() => { - emitEvent({ + emitAppEvent({ name: "cluster", action: "open", params: {