From 3456e5f21d3a21caa6925d2c22adef08e78b8d92 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 22 Nov 2022 11:02:21 -0500 Subject: [PATCH] Fix crash when upgrading helm releases - Fixes not being able to upgrade helm releases as well. Signed-off-by: Sebastian Malton --- .../request-update.injectable.ts | 18 +++---- .../showing-details-for-helm-release.test.ts | 4 +- .../helm/exec-helm/exec-env.injectable.ts | 26 ++++++++++ .../helm/exec-helm/exec-helm.injectable.ts | 3 ++ .../update-helm-release.injectable.ts | 49 ++++++++++--------- .../update-release-route.injectable.ts | 4 +- .../helm-charts/versions.injectable.ts | 7 +-- .../release-details-model.injectable.tsx | 2 +- .../upgrade-chart-model.injectable.ts | 33 +++++++++---- .../components/dock/upgrade-chart/view.tsx | 6 +-- 10 files changed, 94 insertions(+), 58 deletions(-) create mode 100644 src/main/helm/exec-helm/exec-env.injectable.ts diff --git a/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts b/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts index 829f883a44..8f46a3a210 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api/request-update.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import yaml from "js-yaml"; import { apiBaseInjectionToken } from "../../api-base"; import { urlBuilderFor } from "../../../utils/buildUrl"; +import type { AsyncResult } from "../../../utils/async-result"; interface HelmReleaseUpdatePayload { repo: string; @@ -18,7 +18,7 @@ export type RequestHelmReleaseUpdate = ( name: string, namespace: string, payload: HelmReleaseUpdatePayload -) => Promise<{ updateWasSuccessful: true } | { updateWasSuccessful: false; error: unknown }>; +) => Promise>; const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name"); @@ -28,22 +28,20 @@ const requestHelmReleaseUpdateInjectable = getInjectable({ instantiate: (di): RequestHelmReleaseUpdate => { const apiBase = di.inject(apiBaseInjectionToken); - return async (name, namespace, { repo, chart, values, ...data }) => { + return async (name, namespace, { repo, chart, values, version }) => { try { - const x = await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), { + await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), { data: { chart: `${repo}/${chart}`, - values: yaml.load(values), - ...data, + values, + version, }, }); - - console.log(x); } catch (e) { - return { updateWasSuccessful: false, error: e }; + return { callWasSuccessful: false, error: e }; } - return { updateWasSuccessful: true }; + return { callWasSuccessful: true }; }; }, }); diff --git a/src/features/helm-releases/showing-details-for-helm-release.test.ts b/src/features/helm-releases/showing-details-for-helm-release.test.ts index 57bf101414..b743ce013a 100644 --- a/src/features/helm-releases/showing-details-for-helm-release.test.ts +++ b/src/features/helm-releases/showing-details-for-helm-release.test.ts @@ -552,7 +552,7 @@ describe("showing details for helm release", () => { requestHelmReleaseConfigurationMock.mockClear(); await requestHelmReleaseUpdateMock.resolve({ - updateWasSuccessful: true, + callWasSuccessful: true, }); }); @@ -591,7 +591,7 @@ describe("showing details for helm release", () => { requestHelmReleaseConfigurationMock.mockClear(); await requestHelmReleaseUpdateMock.resolve({ - updateWasSuccessful: false, + callWasSuccessful: false, error: "some-error", }); }); diff --git a/src/main/helm/exec-helm/exec-env.injectable.ts b/src/main/helm/exec-helm/exec-env.injectable.ts new file mode 100644 index 0000000000..4d7610a2f7 --- /dev/null +++ b/src/main/helm/exec-helm/exec-env.injectable.ts @@ -0,0 +1,26 @@ +/** + * 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 { computed } from "mobx"; +import userStoreInjectable from "../../../common/user-store/user-store.injectable"; + +const execHelmEnvInjectable = getInjectable({ + id: "exec-helm-env", + instantiate: (di) => { + const userStore = di.inject(userStoreInjectable); + + return computed(() => { + const { + HTTPS_PROXY = userStore.httpsProxy, + ...env + } = process.env; + + return { HTTPS_PROXY, ...env }; + }); + }, + causesSideEffects: true, +}); + +export default execHelmEnvInjectable; diff --git a/src/main/helm/exec-helm/exec-helm.injectable.ts b/src/main/helm/exec-helm/exec-helm.injectable.ts index 39f45a3f7d..034ab43248 100644 --- a/src/main/helm/exec-helm/exec-helm.injectable.ts +++ b/src/main/helm/exec-helm/exec-helm.injectable.ts @@ -7,6 +7,7 @@ import type { ExecFileException } from "child_process"; import execFileInjectable from "../../../common/fs/exec-file.injectable"; import type { AsyncResult } from "../../../common/utils/async-result"; import helmBinaryPathInjectable from "../helm-binary-path.injectable"; +import execHelmEnvInjectable from "./exec-env.injectable"; export type ExecHelm = (args: string[]) => Promise>; @@ -15,10 +16,12 @@ const execHelmInjectable = getInjectable({ instantiate: (di): ExecHelm => { const execFile = di.inject(execFileInjectable); + const execHelmEnv = di.inject(execHelmEnvInjectable); const helmBinaryPath = di.inject(helmBinaryPathInjectable); return async (args) => execFile(helmBinaryPath, args, { maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB + env: execHelmEnv.get(), }); }, }); diff --git a/src/main/helm/helm-service/update-helm-release.injectable.ts b/src/main/helm/helm-service/update-helm-release.injectable.ts index e07269ce6a..f428e7a782 100644 --- a/src/main/helm/helm-service/update-helm-release.injectable.ts +++ b/src/main/helm/helm-service/update-helm-release.injectable.ts @@ -5,16 +5,15 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import loggerInjectable from "../../../common/logger.injectable"; -import type { JsonObject } from "type-fest"; -import { execHelm } from "../exec"; import tempy from "tempy"; -import fse from "fs-extra"; -import yaml from "js-yaml"; import getHelmReleaseInjectable from "./get-helm-release.injectable"; +import writeFileInjectable from "../../../common/fs/write-file.injectable"; +import removePathInjectable from "../../../common/fs/remove-path.injectable"; +import execHelmInjectable from "../exec-helm/exec-helm.injectable"; export interface UpdateChartArgs { chart: string; - values: JsonObject; + values: string; version: string; } @@ -24,40 +23,42 @@ const updateHelmReleaseInjectable = getInjectable({ instantiate: (di) => { const logger = di.inject(loggerInjectable); const getHelmRelease = di.inject(getHelmReleaseInjectable); + const writeFile = di.inject(writeFileInjectable); + const removePath = di.inject(removePathInjectable); + const execHelm = di.inject(execHelmInjectable); return async (cluster: Cluster, releaseName: string, namespace: string, data: UpdateChartArgs) => { const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); - - logger.debug("Upgrade release"); - const valuesFilePath = tempy.file({ name: "values.yaml" }); - await fse.writeFile(valuesFilePath, yaml.dump(data.values)); - - const args = [ - "upgrade", - releaseName, - data.chart, - "--version", data.version, - "--values", valuesFilePath, - "--namespace", namespace, - "--kubeconfig", proxyKubeconfig, - ]; + logger.debug(`[HELM]: upgrading "${releaseName}" in "${namespace}" to ${data.version}`); try { - const output = await execHelm(args); + await writeFile(valuesFilePath, data.values); + + const result = await execHelm([ + "upgrade", + releaseName, + data.chart, + "--version", data.version, + "--values", valuesFilePath, + "--namespace", namespace, + "--kubeconfig", proxyKubeconfig, + ]); + + if (result.callWasSuccessful === false) { + throw result.error; // keep the same interface + } return { - log: output, + log: result.response, release: await getHelmRelease(cluster, releaseName, namespace), }; } finally { - await fse.unlink(valuesFilePath); + await removePath(valuesFilePath); } }; }, - - causesSideEffects: true, }); export default updateHelmReleaseInjectable; diff --git a/src/main/routes/helm/releases/update-release-route.injectable.ts b/src/main/routes/helm/releases/update-release-route.injectable.ts index 573c85d962..5993d7922d 100644 --- a/src/main/routes/helm/releases/update-release-route.injectable.ts +++ b/src/main/routes/helm/releases/update-release-route.injectable.ts @@ -17,8 +17,8 @@ const updateChartArgsValidator = Joi.object { const helmCharts = di.inject(helmChartsInjectable); - - const requestVersionsOfHelmChart = di.inject( - requestVersionsOfHelmChartInjectable, - ); + const requestVersionsOfHelmChart = di.inject(requestVersionsOfHelmChartInjectable); return asyncComputed({ getValueFromObservedPromise: async () => { @@ -25,8 +22,6 @@ const helmChartVersionsInjectable = getInjectable({ return requestVersionsOfHelmChart(release, helmCharts.value.get()); }, - - valueWhenPending: [], }); }, diff --git a/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx b/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx index fc9a1ee9a9..acd65374b9 100644 --- a/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx +++ b/src/renderer/components/+helm-releases/release-details/release-details-model/release-details-model.injectable.tsx @@ -128,7 +128,7 @@ export class ReleaseDetailsModel { this.configuration.isSaving.set(false); }); - if (!result.updateWasSuccessful) { + if (!result.callWasSuccessful) { this.dependencies.showCheckedErrorNotification( result.error, "Unknown error occured while updating release", diff --git a/src/renderer/components/dock/upgrade-chart/upgrade-chart-model.injectable.ts b/src/renderer/components/dock/upgrade-chart/upgrade-chart-model.injectable.ts index 572fa1be76..bf3d9a0214 100644 --- a/src/renderer/components/dock/upgrade-chart/upgrade-chart-model.injectable.ts +++ b/src/renderer/components/dock/upgrade-chart/upgrade-chart-model.injectable.ts @@ -13,6 +13,7 @@ import releasesInjectable from "../../+helm-releases/releases.injectable"; import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable"; import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; import requestHelmReleaseConfigurationInjectable from "../../../../common/k8s-api/endpoints/helm-releases.api/request-configuration.injectable"; +import type { AsyncResult } from "../../../../common/utils/async-result"; import { waitUntilDefined } from "../../../utils"; import type { SelectOption } from "../../select"; import type { DockTab } from "../dock/store"; @@ -31,11 +32,7 @@ export interface UpgradeChartModel { readonly value: IComputedValue; set: (value: SingleValue>) => void; }; - submit: () => Promise; -} - -export interface UpgradeChartSubmitResult { - completedSuccessfully: boolean; + submit: () => Promise>; } const upgradeChartModelInjectable = getInjectable({ @@ -105,13 +102,23 @@ const upgradeChartModelInjectable = getInjectable({ submit: async () => { const selectedVersion = version.value.get(); - if (!selectedVersion || configrationEditError.get()) { + if (!selectedVersion) { return { - completedSuccessfully: false, + callWasSuccessful: false, + error: "No selected version", }; } - await updateRelease( + const editError = configrationEditError.get(); + + if (editError) { + return { + callWasSuccessful: false, + error: editError, + }; + } + + const result = await updateRelease( release.getName(), release.getNs(), { @@ -120,10 +127,16 @@ const upgradeChartModelInjectable = getInjectable({ ...selectedVersion, }, ); - storedConfiguration.invalidate(); + + if (result.callWasSuccessful === true) { + storedConfiguration.invalidate(); + + return { callWasSuccessful: true }; + } return { - completedSuccessfully: true, + callWasSuccessful: false, + error: String(result.error), }; }, }; diff --git a/src/renderer/components/dock/upgrade-chart/view.tsx b/src/renderer/components/dock/upgrade-chart/view.tsx index f17a192c3a..b25f3f24fc 100644 --- a/src/renderer/components/dock/upgrade-chart/view.tsx +++ b/src/renderer/components/dock/upgrade-chart/view.tsx @@ -33,9 +33,9 @@ interface Dependencies { export class NonInjectedUpgradeChart extends React.Component { upgrade = async () => { const { model } = this.props; - const { completedSuccessfully } = await model.submit(); + const result = await model.submit(); - if (completedSuccessfully) { + if (result.callWasSuccessful) { return (

{"Release "} @@ -46,7 +46,7 @@ export class NonInjectedUpgradeChart extends React.Component