From 6142aad5ab520662a3655253b654b5e74c7444f4 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 24 Nov 2022 05:46:53 -0800 Subject: [PATCH] Make ResourceStack fully injectable (#6591) Signed-off-by: Sebastian Malton Signed-off-by: Sebastian Malton --- src/common/ipc/cluster.ts | 2 - .../k8s/create-resource-stack.injectable.ts | 33 ++++ src/common/k8s/resource-stack.ts | 142 +++++++++--------- src/common/kube-helpers/channels.ts | 43 ++++++ src/extensions/common-api/k8s-api.ts | 25 ++- .../setup-ipc-main-handlers.injectable.ts | 3 - .../setup-ipc-main-handlers.ts | 43 +----- .../kubectl/apply-all-handler.injectable.ts | 38 +++++ .../kubectl/delete-all-handler.injectable.ts | 39 +++++ .../kubectl/kubectl-apply-all.injectable.ts | 19 +++ .../kubectl/kubectl-delete-all.injectable.ts | 19 +++ src/main/resource-applier/resource-applier.ts | 25 +-- src/renderer/ipc/index.ts | 10 +- src/renderer/kubectl/apply-all.injectable.ts | 19 +++ src/renderer/kubectl/delete-all.injectable.ts | 19 +++ 15 files changed, 341 insertions(+), 138 deletions(-) create mode 100644 src/common/k8s/create-resource-stack.injectable.ts create mode 100644 src/common/kube-helpers/channels.ts create mode 100644 src/main/kubectl/apply-all-handler.injectable.ts create mode 100644 src/main/kubectl/delete-all-handler.injectable.ts create mode 100644 src/main/kubectl/kubectl-apply-all.injectable.ts create mode 100644 src/main/kubectl/kubectl-delete-all.injectable.ts create mode 100644 src/renderer/kubectl/apply-all.injectable.ts create mode 100644 src/renderer/kubectl/delete-all.injectable.ts diff --git a/src/common/ipc/cluster.ts b/src/common/ipc/cluster.ts index 803069c00e4e..53061abf6017 100644 --- a/src/common/ipc/cluster.ts +++ b/src/common/ipc/cluster.ts @@ -7,8 +7,6 @@ export const clusterActivateHandler = "cluster:activate"; export const clusterSetFrameIdHandler = "cluster:set-frame-id"; export const clusterVisibilityHandler = "cluster:visibility"; export const clusterDisconnectHandler = "cluster:disconnect"; -export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all"; -export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all"; export const clusterStates = "cluster:states"; /** diff --git a/src/common/k8s/create-resource-stack.injectable.ts b/src/common/k8s/create-resource-stack.injectable.ts new file mode 100644 index 000000000000..083d240b3a24 --- /dev/null +++ b/src/common/k8s/create-resource-stack.injectable.ts @@ -0,0 +1,33 @@ +/** + * 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 type { KubernetesCluster } from "../catalog-entities"; +import readDirectoryInjectable from "../fs/read-directory.injectable"; +import readFileInjectable from "../fs/read-file.injectable"; +import { kubectlApplyAllInjectionToken, kubectlDeleteAllInjectionToken } from "../kube-helpers/channels"; +import loggerInjectable from "../logger.injectable"; +import joinPathsInjectable from "../path/join-paths.injectable"; +import type { ResourceApplyingStack, ResourceStackDependencies } from "./resource-stack"; +import { ResourceStack } from "./resource-stack"; + +export type CreateResourceStack = (cluster: KubernetesCluster, name: string) => ResourceApplyingStack; + +const createResourceStackInjectable = getInjectable({ + id: "create-resource-stack", + instantiate: (di): CreateResourceStack => { + const deps: ResourceStackDependencies = { + joinPaths: di.inject(joinPathsInjectable), + kubectlApplyAll: di.inject(kubectlApplyAllInjectionToken), + kubectlDeleteAll: di.inject(kubectlDeleteAllInjectionToken), + logger: di.inject(loggerInjectable), + readDirectory: di.inject(readDirectoryInjectable), + readFile: di.inject(readFileInjectable), + }; + + return (cluster, name) => new ResourceStack(deps, cluster, name); + }, +}); + +export default createResourceStackInjectable; diff --git a/src/common/k8s/resource-stack.ts b/src/common/k8s/resource-stack.ts index ccb63f71c77e..771b48b41373 100644 --- a/src/common/k8s/resource-stack.ts +++ b/src/common/k8s/resource-stack.ts @@ -2,22 +2,39 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import fse from "fs-extra"; -import path from "path"; import hb from "handlebars"; import type { KubernetesCluster } from "../catalog-entities"; -import logger from "../../main/logger"; -import { app } from "electron"; -import { ClusterStore } from "../cluster-store/cluster-store"; 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"; +import type { AsyncResult } from "../utils/async-result"; +import type { Logger } from "../logger"; +import type { KubectlApplyAll, KubectlDeleteAll } from "../kube-helpers/channels"; +import type { ReadDirectory } from "../fs/read-directory.injectable"; +import type { JoinPaths } from "../path/join-paths.injectable"; +import type { ReadFile } from "../fs/read-file.injectable"; +import { hasTypedProperty, isObject } from "../utils"; + +export interface ResourceApplyingStack { + kubectlApplyFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise; + kubectlDeleteFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise; +} + +export interface ResourceStackDependencies { + readonly logger: Logger; + kubectlApplyAll: KubectlApplyAll; + kubectlDeleteAll: KubectlDeleteAll; + readDirectory: ReadDirectory; + joinPaths: JoinPaths; + readFile: ReadFile; +} export class ResourceStack { - constructor(protected cluster: KubernetesCluster, protected name: string) {} + constructor( + protected readonly dependencies: ResourceStackDependencies, + protected readonly cluster: KubernetesCluster, + protected readonly name: string, + ) {} /** * @@ -26,8 +43,15 @@ export class ResourceStack { */ async kubectlApplyFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise { const resources = await this.renderTemplates(folderPath, templateContext); + const result = await this.applyResources(resources, extraArgs); + + if (result.callWasSuccessful) { + return result.response; + } + + this.dependencies.logger.warn(`[RESOURCE-STACK]: failed to apply resources: ${result.error}`); - return this.applyResources(resources, extraArgs); + return ""; } /** @@ -37,68 +61,43 @@ export class ResourceStack { */ async kubectlDeleteFolder(folderPath: string, templateContext?: any, extraArgs?: string[]): Promise { const resources = await this.renderTemplates(folderPath, templateContext); + const result = await this.deleteResources(resources, extraArgs); - return this.deleteResources(resources, extraArgs); - } - - protected async applyResources(resources: string[], extraArgs?: string[]): Promise { - const clusterModel = ClusterStore.getInstance().getById(this.cluster.getId()); - - if (!clusterModel) { - throw new Error(`cluster not found`); + if (result.callWasSuccessful) { + return result.response; } - let kubectlArgs = extraArgs || []; - - kubectlArgs = this.appendKubectlArgs(kubectlArgs); - - if (app) { - const createResourceApplier = asLegacyGlobalFunctionForExtensionApi(createResourceApplierInjectable); - - return await createResourceApplier(clusterModel).kubectlApplyAll(resources, kubectlArgs); - } else { - const response = await requestKubectlApplyAll(this.cluster.getId(), resources, kubectlArgs); + this.dependencies.logger.warn(`[RESOURCE-STACK]: failed to delete resources: ${result.error}`); - if (response.stderr) { - throw new Error(response.stderr); - } - - return response.stdout ?? ""; - } + return ""; } - protected async deleteResources(resources: string[], extraArgs?: string[]): Promise { - const clusterModel = ClusterStore.getInstance().getById(this.cluster.getId()); - - if (!clusterModel) { - throw new Error(`cluster not found`); - } - - let kubectlArgs = extraArgs || []; + protected async applyResources(resources: string[], extraArgs: string[] = []): Promise> { + const kubectlArgs = [...extraArgs, ...this.getAdditionalArgs(extraArgs)]; - kubectlArgs = this.appendKubectlArgs(kubectlArgs); - - if (app) { - const createResourceApplier = asLegacyGlobalFunctionForExtensionApi(createResourceApplierInjectable); + return this.dependencies.kubectlApplyAll({ + clusterId: this.cluster.getId(), + resources, + extraArgs: kubectlArgs, + }); + } - return await createResourceApplier(clusterModel).kubectlDeleteAll(resources, kubectlArgs); - } else { - const response = await requestKubectlDeleteAll(this.cluster.getId(), resources, kubectlArgs); + protected async deleteResources(resources: string[], extraArgs: string[] = []): Promise> { + const kubectlArgs = [...extraArgs, ...this.getAdditionalArgs(extraArgs)]; - if (response.stderr) { - throw new Error(response.stderr); - } - - return response.stdout ?? ""; - } + return this.dependencies.kubectlDeleteAll({ + clusterId: this.cluster.getId(), + resources, + extraArgs: kubectlArgs, + }); } - protected appendKubectlArgs(kubectlArgs: string[]) { + protected getAdditionalArgs(kubectlArgs: string[]): string[] { if (!kubectlArgs.includes("-l") && !kubectlArgs.includes("--label")) { - return kubectlArgs.concat(["-l", `app.kubernetes.io/name=${this.name}`]); + return ["-l", `app.kubernetes.io/name=${this.name}`]; } - return kubectlArgs; + return []; } protected async renderTemplates(folderPath: string, templateContext: any): Promise { @@ -106,16 +105,16 @@ export class ResourceStack { const di = getLegacyGlobalDiForExtensionApi(); const productName = di.inject(productNameInjectable); - logger.info(`[RESOURCE-STACK]: render templates from ${folderPath}`); - const files = await fse.readdir(folderPath); + this.dependencies.logger.info(`[RESOURCE-STACK]: render templates from ${folderPath}`); + const files = await this.dependencies.readDirectory(folderPath); - for(const filename of files) { - const file = path.join(folderPath, filename); - const raw = await fse.readFile(file); + for (const filename of files) { + const file = this.dependencies.joinPaths(folderPath, filename); + const raw = await this.dependencies.readFile(file); const data = ( filename.endsWith(".hb") - ? hb.compile(raw.toString())(templateContext) - : raw.toString() + ? hb.compile(raw)(templateContext) + : raw ).trim(); if (!data) { @@ -127,16 +126,15 @@ export class ResourceStack { continue; } - const resource = entry as Record; + if (hasTypedProperty(entry, "metadata", isObject)) { + const labels = (entry.metadata.labels ??= {}) as Partial>; - if (typeof resource.metadata === "object") { - resource.metadata.labels ??= {}; - resource.metadata.labels["app.kubernetes.io/name"] = this.name; - resource.metadata.labels["app.kubernetes.io/managed-by"] = productName; - resource.metadata.labels["app.kubernetes.io/created-by"] = "resource-stack"; + labels["app.kubernetes.io/name"] = this.name; + labels["app.kubernetes.io/managed-by"] = productName; + labels["app.kubernetes.io/created-by"] = "resource-stack"; } - resources.push(yaml.dump(resource)); + resources.push(yaml.dump(entry)); } } diff --git a/src/common/kube-helpers/channels.ts b/src/common/kube-helpers/channels.ts new file mode 100644 index 000000000000..4782f643672d --- /dev/null +++ b/src/common/kube-helpers/channels.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Asyncify } from "type-fest"; +import type { RequestChannelHandler } from "../../main/utils/channel/channel-listeners/listener-tokens"; +import type { ClusterId } from "../cluster-types"; +import type { AsyncResult } from "../utils/async-result"; +import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; + +export interface KubectlApplyAllArgs { + clusterId: ClusterId; + resources: string[]; + extraArgs: string[]; +} + +export const kubectlApplyAllChannel: RequestChannel> = { + id: "kubectl-apply-all", +}; + +export type KubectlApplyAll = Asyncify>; + +export const kubectlApplyAllInjectionToken = getInjectionToken({ + id: "kubectl-apply-all", +}); + +export interface KubectlDeleteAllArgs { + clusterId: ClusterId; + resources: string[]; + extraArgs: string[]; +} + +export const kubectlDeleteAllChannel: RequestChannel> = { + id: "kubectl-delete-all", +}; + +export type KubectlDeleteAll = Asyncify>; + +export const kubectlDeleteAllInjectionToken = getInjectionToken({ + id: "kubectl-delete-all", +}); diff --git a/src/extensions/common-api/k8s-api.ts b/src/extensions/common-api/k8s-api.ts index f5bf1e4953a6..e5c7013bc70a 100644 --- a/src/extensions/common-api/k8s-api.ts +++ b/src/extensions/common-api/k8s-api.ts @@ -7,12 +7,14 @@ // It is here to consolidate the common parts which are exported to `Main` // and to `Renderer` -export { ResourceStack } from "../../common/k8s/resource-stack"; import apiManagerInjectable from "../../common/k8s-api/api-manager/manager.injectable"; import createKubeApiForClusterInjectable from "../../common/k8s-api/create-kube-api-for-cluster.injectable"; import createKubeApiForRemoteClusterInjectable from "../../common/k8s-api/create-kube-api-for-remote-cluster.injectable"; +import createResourceStackInjectable from "../../common/k8s/create-resource-stack.injectable"; +import type { ResourceApplyingStack } from "../../common/k8s/resource-stack"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; +import type { KubernetesCluster } from "./catalog"; export const apiManager = asLegacyGlobalForExtensionApi(apiManagerInjectable); export const forCluster = asLegacyGlobalFunctionForExtensionApi(createKubeApiForClusterInjectable); @@ -20,6 +22,27 @@ export const forRemoteCluster = asLegacyGlobalFunctionForExtensionApi(createKube export { KubeApi } from "../../common/k8s-api/kube-api"; +export const createResourceStack = asLegacyGlobalFunctionForExtensionApi(createResourceStackInjectable); + +/** + * @deprecated Switch to using `Common.createResourceStack` instead + */ +export class ResourceStack implements ResourceApplyingStack { + private readonly impl: ResourceApplyingStack; + + constructor(cluster: KubernetesCluster, name: string) { + this.impl = createResourceStack(cluster, name); + } + + kubectlApplyFolder(folderPath: string, templateContext?: any, extraArgs?: string[] | undefined): Promise { + return this.impl.kubectlApplyFolder(folderPath, templateContext, extraArgs); + } + + kubectlDeleteFolder(folderPath: string, templateContext?: any, extraArgs?: string[] | undefined): Promise { + return this.impl.kubectlDeleteFolder(folderPath, templateContext, extraArgs); + } +} + /** * @deprecated This type is unused */ 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 bc642e9df172..7bb4a8e54066 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,7 +12,6 @@ import catalogEntityRegistryInjectable from "../../../catalog/entity-registry.in 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", @@ -25,7 +24,6 @@ const setupIpcMainHandlersInjectable = getInjectable({ 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", @@ -39,7 +37,6 @@ const setupIpcMainHandlersInjectable = getInjectable({ 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 89814d92f2fa..cb6a642a6af5 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 @@ -5,7 +5,7 @@ import type { IpcMainInvokeEvent } from "electron"; import { BrowserWindow, Menu } from "electron"; import { clusterFrameMap } from "../../../../common/cluster-frames"; -import { clusterActivateHandler, clusterSetFrameIdHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler } from "../../../../common/ipc/cluster"; +import { clusterActivateHandler, clusterSetFrameIdHandler, clusterDisconnectHandler } from "../../../../common/ipc/cluster"; import type { ClusterId } from "../../../../common/cluster-types"; import { ClusterStore } from "../../../../common/cluster-store/cluster-store"; import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc"; @@ -23,7 +23,6 @@ import type { Composite } from "../../../../common/utils/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>; @@ -32,7 +31,6 @@ interface Dependencies { operatingSystemTheme: IComputedValue; askUserForFilePaths: AskUserForFilePaths; emitAppEvent: EmitAppEvent; - createResourceApplier: CreateResourceApplier; } export const setupIpcMainHandlers = ({ @@ -42,7 +40,6 @@ export const setupIpcMainHandlers = ({ operatingSystemTheme, askUserForFilePaths, emitAppEvent, - createResourceApplier, }: Dependencies) => { ipcMainHandle(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { return ClusterStore.getInstance() @@ -71,44 +68,6 @@ export const setupIpcMainHandlers = ({ } }); - ipcMainHandle(clusterKubectlApplyAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - emitAppEvent({ name: "cluster", action: "kubectl-apply-all" }); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - const applier = createResourceApplier(cluster); - - try { - const stdout = await applier.kubectlApplyAll(resources, extraArgs); - - return { stdout }; - } catch (error: any) { - return { stderr: error }; - } - } else { - throw `${clusterId} is not a valid cluster id`; - } - }); - - ipcMainHandle(clusterKubectlDeleteAllHandler, async (event, clusterId: ClusterId, resources: string[], extraArgs: string[]) => { - emitAppEvent({ name: "cluster", action: "kubectl-delete-all" }); - const cluster = ClusterStore.getInstance().getById(clusterId); - - if (cluster) { - const applier = createResourceApplier(cluster); - - try { - const stdout = await applier.kubectlDeleteAll(resources, extraArgs); - - return { stdout }; - } catch (error: any) { - return { stderr: error }; - } - } else { - throw `${clusterId} is not a valid cluster id`; - } - }); - ipcMainHandle(windowActionHandleChannel, (event, action) => handleWindowAction(action)); ipcMainOn(windowLocationChangedChannel, () => onLocationChange()); diff --git a/src/main/kubectl/apply-all-handler.injectable.ts b/src/main/kubectl/apply-all-handler.injectable.ts new file mode 100644 index 000000000000..ca9d13577b6e --- /dev/null +++ b/src/main/kubectl/apply-all-handler.injectable.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; +import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; +import { kubectlApplyAllChannel } from "../../common/kube-helpers/channels"; +import createResourceApplierInjectable from "../resource-applier/create-resource-applier.injectable"; +import { getRequestChannelListenerInjectable } from "../utils/channel/channel-listeners/listener-tokens"; + +const kubectlApplyAllChannelHandlerInjectable = getRequestChannelListenerInjectable({ + channel: kubectlApplyAllChannel, + handler: (di) => { + const getClusterById = di.inject(getClusterByIdInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); + const createResourceApplier = di.inject(createResourceApplierInjectable); + + return async ({ + clusterId, + extraArgs, + resources, + }) => { + emitAppEvent({ name: "cluster", action: "kubectl-apply-all" }); + const cluster = getClusterById(clusterId); + + if (!cluster) { + return { + callWasSuccessful: false, + error: `No cluster found for clusterId="${clusterId}"`, + }; + } + + return createResourceApplier(cluster).kubectlApplyAll(resources, extraArgs); + }; + }, +}); + +export default kubectlApplyAllChannelHandlerInjectable; diff --git a/src/main/kubectl/delete-all-handler.injectable.ts b/src/main/kubectl/delete-all-handler.injectable.ts new file mode 100644 index 000000000000..00a8db67079a --- /dev/null +++ b/src/main/kubectl/delete-all-handler.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; +import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; +import { kubectlDeleteAllChannel } from "../../common/kube-helpers/channels"; +import createResourceApplierInjectable from "../resource-applier/create-resource-applier.injectable"; +import { getRequestChannelListenerInjectable } from "../utils/channel/channel-listeners/listener-tokens"; + +const kubectlDeleteAllChannelHandlerInjectable = getRequestChannelListenerInjectable({ + channel: kubectlDeleteAllChannel, + handler: (di) => { + const emitAppEvent = di.inject(emitAppEventInjectable); + const getClusterById = di.inject(getClusterByIdInjectable); + const createResourceApplier = di.inject(createResourceApplierInjectable); + + return async ({ + clusterId, + extraArgs, + resources, + }) => { + emitAppEvent({ name: "cluster", action: "kubectl-delete-all" }); + + const cluster = getClusterById(clusterId); + + if (!cluster) { + return { + callWasSuccessful: false, + error: `No cluster found for clusterId="${clusterId}"`, + }; + } + + return createResourceApplier(cluster).kubectlDeleteAll(resources, extraArgs); + }; + }, +}); + +export default kubectlDeleteAllChannelHandlerInjectable; diff --git a/src/main/kubectl/kubectl-apply-all.injectable.ts b/src/main/kubectl/kubectl-apply-all.injectable.ts new file mode 100644 index 000000000000..f65dd78c9b9f --- /dev/null +++ b/src/main/kubectl/kubectl-apply-all.injectable.ts @@ -0,0 +1,19 @@ +/** + * 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 { kubectlApplyAllInjectionToken } from "../../common/kube-helpers/channels"; +import kubectlApplyAllChannelHandlerInjectable from "./apply-all-handler.injectable"; + +const kubectlApplyAllInjectable = getInjectable({ + id: "kubectl-apply-all", + instantiate: (di) => { + const channelHandler = di.inject(kubectlApplyAllChannelHandlerInjectable); + + return async (req) => channelHandler.handler(req); + }, + injectionToken: kubectlApplyAllInjectionToken, +}); + +export default kubectlApplyAllInjectable; diff --git a/src/main/kubectl/kubectl-delete-all.injectable.ts b/src/main/kubectl/kubectl-delete-all.injectable.ts new file mode 100644 index 000000000000..e97342b84f24 --- /dev/null +++ b/src/main/kubectl/kubectl-delete-all.injectable.ts @@ -0,0 +1,19 @@ +/** + * 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 { kubectlDeleteAllInjectionToken } from "../../common/kube-helpers/channels"; +import kubectlDeleteAllChannelHandlerInjectable from "./delete-all-handler.injectable"; + +const kubectlDeleteAllInjectable = getInjectable({ + id: "kubectl-delete-all", + instantiate: (di) => { + const channel = di.inject(kubectlDeleteAllChannelHandlerInjectable); + + return async (req) => channel.handler(req); + }, + injectionToken: kubectlDeleteAllInjectionToken, +}); + +export default kubectlDeleteAllInjectable; diff --git a/src/main/resource-applier/resource-applier.ts b/src/main/resource-applier/resource-applier.ts index 95e893a0a9b3..3384b1cbf220 100644 --- a/src/main/resource-applier/resource-applier.ts +++ b/src/main/resource-applier/resource-applier.ts @@ -14,6 +14,7 @@ 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"; +import type { AsyncResult } from "../../common/utils/async-result"; export interface ResourceApplierDependencies { emitAppEvent: EmitAppEvent; @@ -66,13 +67,13 @@ export class ResourceApplier { throw result.error.stderr || result.error.message; } - async create(resource: string): Promise { + async create(resource: string): Promise> { this.dependencies.emitAppEvent({ name: "resource", action: "apply" }); return this.kubectlApply(this.sanitizeObject(resource)); } - protected async kubectlApply(content: string): Promise { + protected async kubectlApply(content: string): Promise> { const kubectl = await this.cluster.ensureKubectl(); const kubectlPath = await kubectl.getPath(); const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath(); @@ -99,24 +100,27 @@ export class ResourceApplier { const result = await this.dependencies.execFile(kubectlPath, args); if (result.callWasSuccessful) { - return result.response; + return result; } - throw result.error.stderr || result.error.message; + return { + callWasSuccessful: false, + error: result.error.stderr || result.error.message, + }; } finally { await this.dependencies.deleteFile(fileName); } } - public async kubectlApplyAll(resources: string[], extraArgs = ["-o", "json"]): Promise { + public async kubectlApplyAll(resources: string[], extraArgs = ["-o", "json"]): Promise> { return this.kubectlCmdAll("apply", resources, extraArgs); } - public async kubectlDeleteAll(resources: string[], extraArgs?: string[]): Promise { + public async kubectlDeleteAll(resources: string[], extraArgs?: string[]): Promise> { return this.kubectlCmdAll("delete", resources, extraArgs); } - protected async kubectlCmdAll(subCmd: string, resources: string[], parentArgs: 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(); @@ -138,14 +142,17 @@ export class ResourceApplier { const result = await this.dependencies.execFile(kubectlPath, args); if (result.callWasSuccessful) { - return result.response; + return result; } this.dependencies.logger.error(`[RESOURCE-APPLIER] kubectl errored: ${result.error.message}`); const splitError = result.error.stderr.split(`.yaml": `); - throw splitError[1] || result.error.message; + return { + callWasSuccessful: false, + error: splitError[1] || result.error.message, + }; } protected sanitizeObject(resource: string) { diff --git a/src/renderer/ipc/index.ts b/src/renderer/ipc/index.ts index 5c0cd13c4e4f..e9407a1af58a 100644 --- a/src/renderer/ipc/index.ts +++ b/src/renderer/ipc/index.ts @@ -4,7 +4,7 @@ */ import type { OpenDialogOptions } from "electron"; -import { clusterActivateHandler, clusterDisconnectHandler, clusterKubectlApplyAllHandler, clusterKubectlDeleteAllHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster"; +import { clusterActivateHandler, clusterDisconnectHandler, clusterSetFrameIdHandler, clusterStates } from "../../common/ipc/cluster"; import type { ClusterId, ClusterState } from "../../common/cluster-types"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel, type WindowAction } from "../../common/ipc/window"; import { openFilePickingDialogChannel } from "../../common/ipc/dialog"; @@ -64,14 +64,6 @@ export function requestInitialClusterStates(): Promise<{ id: string; state: Clus return requestMain(clusterStates); } -export function requestKubectlApplyAll(clusterId: ClusterId, resources: string[], kubectlArgs: string[]): Promise<{ stderr?: string; stdout?: string }> { - return requestMain(clusterKubectlApplyAllHandler, clusterId, resources, kubectlArgs); -} - -export function requestKubectlDeleteAll(clusterId: ClusterId, resources: string[], kubectlArgs: string[]): Promise<{ stderr?: string; stdout?: string }> { - return requestMain(clusterKubectlDeleteAllHandler, clusterId, resources, kubectlArgs); -} - export function requestInitialExtensionDiscovery(): Promise<{ isLoaded: boolean }> { return requestMain(extensionDiscoveryStateChannel); } diff --git a/src/renderer/kubectl/apply-all.injectable.ts b/src/renderer/kubectl/apply-all.injectable.ts new file mode 100644 index 000000000000..989017f9d96a --- /dev/null +++ b/src/renderer/kubectl/apply-all.injectable.ts @@ -0,0 +1,19 @@ +/** + * 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 { kubectlApplyAllChannel, kubectlApplyAllInjectionToken } from "../../common/kube-helpers/channels"; +import requestFromChannelInjectable from "../utils/channel/request-from-channel.injectable"; + +const kubectlApplyAllInjectable = getInjectable({ + id: "kubectl-apply-all", + instantiate: (di) => { + const requestFromChannel = di.inject(requestFromChannelInjectable); + + return (req) => requestFromChannel(kubectlApplyAllChannel, req); + }, + injectionToken: kubectlApplyAllInjectionToken, +}); + +export default kubectlApplyAllInjectable; diff --git a/src/renderer/kubectl/delete-all.injectable.ts b/src/renderer/kubectl/delete-all.injectable.ts new file mode 100644 index 000000000000..586895c75cb7 --- /dev/null +++ b/src/renderer/kubectl/delete-all.injectable.ts @@ -0,0 +1,19 @@ +/** + * 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 { kubectlDeleteAllChannel, kubectlDeleteAllInjectionToken } from "../../common/kube-helpers/channels"; +import requestFromChannelInjectable from "../utils/channel/request-from-channel.injectable"; + +const kubectlDeleteAllInjectable = getInjectable({ + id: "kubectl-delete-all", + instantiate: (di) => { + const requestFromChannel = di.inject(requestFromChannelInjectable); + + return (req) => requestFromChannel(kubectlDeleteAllChannel, req); + }, + injectionToken: kubectlDeleteAllInjectionToken, +}); + +export default kubectlDeleteAllInjectable;