From 18db8d544df6782b8fbfe901c256f3d0e07972d7 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 12 Aug 2022 11:25:56 -0400 Subject: [PATCH] Switch to non-reactive way of gettting possible helm release versions Signed-off-by: Sebastian Malton --- src/common/utils/sort-compare.ts | 79 +++++++------------ src/main/helm/__tests__/helm-service.test.ts | 14 ++-- src/main/helm/helm-chart-manager.ts | 4 +- ...ersions-of-chart-for-release.injectable.ts | 36 +++++++++ .../request-versions.injectable.ts | 28 +++++++ .../helm-charts/versions.injectable.ts | 26 +----- .../+helm-charts/helm-charts/versions.ts | 12 +++ .../upgrade-chart-model.injectable.ts | 10 +-- .../components/dock/upgrade-chart/view.tsx | 4 +- 9 files changed, 122 insertions(+), 91 deletions(-) create mode 100644 src/renderer/components/+helm-charts/helm-charts/request-versions-of-chart-for-release.injectable.ts create mode 100644 src/renderer/components/+helm-charts/helm-charts/request-versions.injectable.ts create mode 100644 src/renderer/components/+helm-charts/helm-charts/versions.ts diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts index ee32ab45c4..35d88c01b9 100644 --- a/src/common/utils/sort-compare.ts +++ b/src/common/utils/sort-compare.ts @@ -3,11 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { SemVer } from "semver"; import semver, { coerce } from "semver"; -import * as iter from "./iter"; -import type { RawHelmChart } from "../k8s-api/endpoints/helm-charts.api"; -import logger from "../logger"; export enum Ordering { LESS = -1, @@ -49,52 +45,31 @@ export function sortCompare(left: T, right: T): Ordering { return Ordering.GREATER; } -interface ChartVersion { - version: string; - __version?: SemVer | null; -} - -export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): Ordering { - if (left.__version && right.__version) { - return semver.compare(right.__version, left.__version); - } - - if (!left.__version && right.__version) { - return Ordering.GREATER; - } - - if (left.__version && !right.__version) { - return Ordering.LESS; - } - - return sortCompare(left.version, right.version); -} - - - -export function sortCharts(charts: RawHelmChart[]) { - interface ExtendedHelmChart extends RawHelmChart { - __version?: SemVer | null; - } - - const chartsWithVersion = Array.from( - iter.map( - charts, - chart => { - const __version = coerce(chart.version, { loose: true }); - - if (!__version) { - logger.warn(`[HELM-SERVICE]: Version from helm chart is not loosely coercable to semver.`, { name: chart.name, version: chart.version, repo: chart.repo }); - } - - (chart as ExtendedHelmChart).__version = __version; - - return chart as ExtendedHelmChart; - }, - ), - ); - - return chartsWithVersion - .sort(sortCompareChartVersions) - .map(chart => (delete chart.__version, chart as RawHelmChart)); +/** + * This function sorts of list of items that have what should be a semver version formated string + * as the field `version` but if it is not loosely coercable to semver falls back to sorting them + * alphanumerically + */ +export function sortBySemverVersion(versioneds: T[]): T[] { + return versioneds + .map(versioned => ({ + __version: coerce(versioned.version, { loose: true }), + raw: versioned, + })) + .sort((left, right) => { + if (left.__version && right.__version) { + return semver.compare(right.__version, left.__version); + } + + if (!left.__version && right.__version) { + return Ordering.GREATER; + } + + if (left.__version && !right.__version) { + return Ordering.LESS; + } + + return sortCompare(left.raw.version, right.raw.version); + }) + .map(({ raw }) => raw); } diff --git a/src/main/helm/__tests__/helm-service.test.ts b/src/main/helm/__tests__/helm-service.test.ts index ea0a28b1e9..982a361f30 100644 --- a/src/main/helm/__tests__/helm-service.test.ts +++ b/src/main/helm/__tests__/helm-service.test.ts @@ -8,7 +8,7 @@ import listHelmChartsInjectable from "../helm-service/list-helm-charts.injectabl import getActiveHelmRepositoriesInjectable from "../repositories/get-active-helm-repositories/get-active-helm-repositories.injectable"; import type { AsyncResult } from "../../../common/utils/async-result"; import type { HelmRepo } from "../../../common/helm/helm-repo"; -import { sortCharts } from "../../../common/utils"; +import { sortBySemverVersion } from "../../../common/utils"; import helmChartManagerInjectable from "../helm-chart-manager.injectable"; describe("Helm Service tests", () => { @@ -203,7 +203,7 @@ describe("Helm Service tests", () => { const charts = new Map([ ["stable", { - "invalid-semver": sortCharts([ + "invalid-semver": sortBySemverVersion([ { apiVersion: "3.0.0", name: "weird-versioning", @@ -237,7 +237,7 @@ const charts = new Map([ created: "now", }, ]), - "apm-server": sortCharts([ + "apm-server": sortBySemverVersion([ { apiVersion: "3.0.0", name: "apm-server", @@ -255,7 +255,7 @@ const charts = new Map([ created: "now", }, ]), - "redis": sortCharts([ + "redis": sortBySemverVersion([ { apiVersion: "3.0.0", name: "apm-server", @@ -275,7 +275,7 @@ const charts = new Map([ ]), }], ["experiment", { - "fairwind": sortCharts([ + "fairwind": sortBySemverVersion([ { apiVersion: "3.0.0", name: "fairwind", @@ -296,7 +296,7 @@ const charts = new Map([ ]), }], ["bitnami", { - "hotdog": sortCharts([ + "hotdog": sortBySemverVersion([ { apiVersion: "3.0.0", name: "hotdog", @@ -314,7 +314,7 @@ const charts = new Map([ created: "now", }, ]), - "pretzel": sortCharts([ + "pretzel": sortBySemverVersion([ { apiVersion: "3.0.0", name: "pretzel", diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index a584d5db56..dfc80a78e4 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -5,7 +5,7 @@ import fs from "fs"; import * as yaml from "js-yaml"; -import { iter, put, sortCharts } from "../../common/utils"; +import { iter, put, sortBySemverVersion } from "../../common/utils"; import { execHelm } from "./exec"; import type { SetRequired } from "type-fest"; import { assert } from "console"; @@ -118,7 +118,7 @@ function normalizeHelmCharts(repoName: string, entries: RepoHelmChartList): Repo Object.entries(entries), ([name, charts]) => [ name, - sortCharts( + sortBySemverVersion( charts.map(chart => ({ ...chart, created: Date.parse(chart.created).toString(), diff --git a/src/renderer/components/+helm-charts/helm-charts/request-versions-of-chart-for-release.injectable.ts b/src/renderer/components/+helm-charts/helm-charts/request-versions-of-chart-for-release.injectable.ts new file mode 100644 index 0000000000..b49d94fb10 --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts/request-versions-of-chart-for-release.injectable.ts @@ -0,0 +1,36 @@ +/** + * 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 { HelmChart } from "../../../../common/k8s-api/endpoints/helm-charts.api"; +import requestHelmChartVersionsInjectable from "../../../../common/k8s-api/endpoints/helm-charts.api/request-versions.injectable"; +import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import { sortBySemverVersion } from "../../../utils"; +import type { HelmChartVersion } from "./versions"; + +/** + * @param release The release to try and figure out what chart versions match for + * @param charts The list of possible helm charts that `release` could be of + */ +export type RequestVersionsOfHelmChart = (release: HelmRelease, charts: HelmChart[]) => Promise; + +const requestVersionsOfHelmChartInjectable = getInjectable({ + id: "request-versions-of-helm-chart-for-helm-release-from-list-of-charts", + instantiate: (di): RequestVersionsOfHelmChart => { + const requestHelmChartVersions = di.inject(requestHelmChartVersionsInjectable); + + return async (release, charts) => { + const rawVersions = await Promise.all(( + charts + .filter(chart => chart.getName() === release.getChart()) + .map(chart => chart.getRepository()) + .map(repo => requestHelmChartVersions(repo, release.getChart())) + )); + + return sortBySemverVersion(rawVersions.flat()); + }; + }, +}); + +export default requestVersionsOfHelmChartInjectable; diff --git a/src/renderer/components/+helm-charts/helm-charts/request-versions.injectable.ts b/src/renderer/components/+helm-charts/helm-charts/request-versions.injectable.ts new file mode 100644 index 0000000000..1dbe685617 --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts/request-versions.injectable.ts @@ -0,0 +1,28 @@ +/** + * 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 requestHelmChartsInjectable from "../../../../common/k8s-api/endpoints/helm-charts.api/request-charts.injectable"; +import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; +import requestVersionsOfHelmChartInjectable from "./request-versions-of-chart-for-release.injectable"; +import type { HelmChartVersion } from "./versions"; + +export type RequestVersionsOfHelmChartFor = (release: HelmRelease) => Promise; + +const requestVersionsOfHelmChartForInjectable = getInjectable({ + id: "request-versions-of-helm-chart-for", + instantiate: (di): RequestVersionsOfHelmChartFor => { + const requestHelmCharts = di.inject(requestHelmChartsInjectable); + const requestVersionsOfHelmChart = di.inject(requestVersionsOfHelmChartInjectable); + + return async (release) => { + const charts = await requestHelmCharts(); + + return requestVersionsOfHelmChart(release, charts); + }; + }, +}); + +export default requestVersionsOfHelmChartForInjectable; + diff --git a/src/renderer/components/+helm-charts/helm-charts/versions.injectable.ts b/src/renderer/components/+helm-charts/helm-charts/versions.injectable.ts index 2a195b6758..db06423ea5 100644 --- a/src/renderer/components/+helm-charts/helm-charts/versions.injectable.ts +++ b/src/renderer/components/+helm-charts/helm-charts/versions.injectable.ts @@ -5,40 +5,20 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { asyncComputed } from "@ogre-tools/injectable-react"; import { when } from "mobx"; -import { coerce } from "semver"; -import requestHelmChartVersionsInjectable from "../../../../common/k8s-api/endpoints/helm-charts.api/request-versions.injectable"; import type { HelmRelease } from "../../../../common/k8s-api/endpoints/helm-releases.api"; -import { sortCompareChartVersions } from "../../../utils"; import helmChartsInjectable from "./helm-charts.injectable"; - -export interface ChartVersion { - repo: string; - version: string; -} - -const sortChartVersions = (versions: ChartVersion[]) => ( - versions - .map(chartVersion => ({ ...chartVersion, __version: coerce(chartVersion.version, { loose: true }) })) - .sort(sortCompareChartVersions) - .map(({ __version, ...chartVersion }) => chartVersion) -); +import requestVersionsOfHelmChartInjectable from "./request-versions-of-chart-for-release.injectable"; const helmChartVersionsInjectable = getInjectable({ id: "helm-chart-versions-loader", instantiate: (di, release) => { - const requestHelmChartVersions = di.inject(requestHelmChartVersionsInjectable); const helmCharts = di.inject(helmChartsInjectable); + const requestVersionsOfHelmChart = di.inject(requestVersionsOfHelmChartInjectable); return asyncComputed(async () => { await when(() => !helmCharts.pending.get()); - const rawVersions = await Promise.all(( - helmCharts.value.get() - .filter(chart => chart.getName() === release.getChart()) - .map(chart => chart.getRepository()) - .map(repo => requestHelmChartVersions(repo, release.getChart())) - )); - return sortChartVersions(rawVersions.flat()); + return requestVersionsOfHelmChart(release, helmCharts.value.get()); }, []); }, lifecycle: lifecycleEnum.keyedSingleton({ diff --git a/src/renderer/components/+helm-charts/helm-charts/versions.ts b/src/renderer/components/+helm-charts/helm-charts/versions.ts new file mode 100644 index 0000000000..950a6931cb --- /dev/null +++ b/src/renderer/components/+helm-charts/helm-charts/versions.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +/** + * A type for the possible versions that a helm release was made from + */ +export interface HelmChartVersion { + repo: string; + version: string; +} 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 e7453a9d01..41b7a5f7bf 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 @@ -7,7 +7,7 @@ import { asyncComputed } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import { action, computed, observable, when } from "mobx"; import type { SingleValue } from "react-select"; -import type { ChartVersion } from "../../+helm-charts/helm-charts/versions.injectable"; +import type { HelmChartVersion } from "../../+helm-charts/helm-charts/versions"; import helmChartVersionsInjectable from "../../+helm-charts/helm-charts/versions.injectable"; import releasesInjectable from "../../+helm-releases/releases.injectable"; import updateReleaseInjectable from "../../+helm-releases/update-release/update-release.injectable"; @@ -20,7 +20,7 @@ import upgradeChartTabStoreInjectable from "./store.injectable"; export interface UpgradeChartModel { readonly release: HelmRelease; - readonly versionOptions: IComputedValue[]>; + readonly versionOptions: IComputedValue[]>; readonly configration: { readonly value: IComputedValue; set: (value: string) => void; @@ -28,8 +28,8 @@ export interface UpgradeChartModel { setError: (error: unknown) => void; }; readonly version: { - readonly value: IComputedValue; - set: (value: SingleValue>) => void; + readonly value: IComputedValue; + set: (value: SingleValue>) => void; }; submit: () => Promise; } @@ -68,7 +68,7 @@ const upgradeChartModelInjectable = getInjectable({ error: computed(() => configrationEditError.get()), setError: action((error) => configrationEditError.set(String(error))), }; - const versionValue = observable.box(undefined, { + const versionValue = observable.box(undefined, { deep: false, }); const version: UpgradeChartModel["version"] = { diff --git a/src/renderer/components/dock/upgrade-chart/view.tsx b/src/renderer/components/dock/upgrade-chart/view.tsx index 61d7747b7b..5a6319c9cd 100644 --- a/src/renderer/components/dock/upgrade-chart/view.tsx +++ b/src/renderer/components/dock/upgrade-chart/view.tsx @@ -17,9 +17,9 @@ import { EditorPanel } from "../editor-panel"; import type { SelectOption } from "../../select"; import { Select } from "../../select"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { ChartVersion } from "../../+helm-charts/helm-charts/versions.injectable"; import type { UpgradeChartModel } from "./upgrade-chart-model.injectable"; import upgradeChartModelInjectable from "./upgrade-chart-model.injectable"; +import type { HelmChartVersion } from "../../+helm-charts/helm-charts/versions"; export interface UpgradeChartProps { className?: string; @@ -82,7 +82,7 @@ export class NonInjectedUpgradeChart extends React.Component Upgrade version - , false> + , false> id="char-version-input" className="chart-version" menuPlacement="top"