From 38276bbbff6b32fe7e29ebcb8c318de6316c09b4 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 20 Oct 2021 08:50:52 -0400 Subject: [PATCH] Update helm cache if files have been modified (#3973) --- src/common/utils/sort-compare.ts | 2 +- src/main/helm/helm-chart-manager.ts | 104 ++++++++++++++++++---------- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts index 799c47f957..7167761151 100644 --- a/src/common/utils/sort-compare.ts +++ b/src/common/utils/sort-compare.ts @@ -111,5 +111,5 @@ export function sortCharts(charts: RawHelmChart[]) { return chartsWithVersion .sort(sortCompareChartVersions) - .map(chart => (delete chart.__version, chart)); + .map(chart => (delete chart.__version, chart as RawHelmChart)); } diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index e99554dcd0..b7afb6ff3d 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -24,10 +24,15 @@ import v8 from "v8"; import * as yaml from "js-yaml"; import type { HelmRepo } from "./helm-repo-manager"; import logger from "../logger"; -import { promiseExec } from "../promise-exec"; +import { promiseExecFile } from "../promise-exec"; import { helmCli } from "./helm-cli"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; -import { sortCharts } from "../../common/utils"; +import { iter, sortCharts } from "../../common/utils"; + +interface ChartCacheEntry { + data: Buffer, + mtimeMs: number, +} export interface HelmCacheFile { apiVersion: string; @@ -35,7 +40,7 @@ export interface HelmCacheFile { } export class HelmChartManager { - static #cache = new Map(); + static #cache = new Map(); private constructor(protected repo: HelmRepo) {} @@ -59,16 +64,17 @@ export class HelmChartManager { } } - private async executeCommand(action: string, name: string, version?: string) { + private async executeCommand(args: string[], name: string, version?: string) { const helm = await helmCli.binaryPath(); - const cmd = [`"${helm}" ${action} ${this.repo.name}/${name}`]; + + args.push(`${this.repo.name}/${name}`); if (version) { - cmd.push("--version", version); + args.push("--version", version); } try { - const { stdout } = await promiseExec(cmd.join(" ")); + const { stdout } = await promiseExecFile(helm, args); return stdout; } catch (error) { @@ -77,47 +83,69 @@ export class HelmChartManager { } public async getReadme(name: string, version?: string) { - return this.executeCommand("show readme", name, version); + return this.executeCommand(["show", "readme"], name, version); } public async getValues(name: string, version?: string) { - return this.executeCommand("show values", name, version); + return this.executeCommand(["show", "values"], name, version); + } + + protected async updateYamlCache() { + const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8"); + const cacheFileStats = await fs.promises.stat(this.repo.cacheFilePath); + const data = yaml.load(cacheFile) as string | number | HelmCacheFile; + + if (!data || typeof data !== "object" || typeof data.entries !== "object") { + throw Object.assign(new TypeError("Helm Cache file does not parse correctly"), { file: this.repo.cacheFilePath, data }); + } + + const normalized = normalizeHelmCharts(this.repo.name, data.entries); + + HelmChartManager.#cache.set(this.repo.name, { + data: v8.serialize(normalized), + mtimeMs: cacheFileStats.mtimeMs, + }); } protected async cachedYaml(): Promise { if (!HelmChartManager.#cache.has(this.repo.name)) { - const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8"); - const data = yaml.load(cacheFile) as string | number | HelmCacheFile; + await this.updateYamlCache(); + } else { + const newStats = await fs.promises.stat(this.repo.cacheFilePath); + const cacheEntry = HelmChartManager.#cache.get(this.repo.name); - if (typeof data !== "object" || !data) { - return {}; + if (cacheEntry.mtimeMs < newStats.mtimeMs) { + await this.updateYamlCache(); } - - /** - * Do some initial preprocessing on the data, so as to avoid needing to do it later - * 1. Set the repo name - * 2. Normalize the created date - * 3. Filter out deprecated items - */ - - const normalized = Object.fromEntries( - Object.entries(data.entries) - .map(([name, charts]) => [ - name, - sortCharts( - charts.map(chart => ({ - ...chart, - created: Date.parse(chart.created).toString(), - repo: this.repo.name, - })), - ), - ] as const) - .filter(([, charts]) => !charts.every(chart => chart.deprecated)) - ); - - HelmChartManager.#cache.set(this.repo.name, v8.serialize(normalized)); } - return v8.deserialize(HelmChartManager.#cache.get(this.repo.name)); + return v8.deserialize(HelmChartManager.#cache.get(this.repo.name).data); } } + +/** + * Do some initial preprocessing on the data, so as to avoid needing to do it later + * 1. Set the repo name + * 2. Normalize the created date + * 3. Filter out charts that only have deprecated entries + */ +function normalizeHelmCharts(repoName: string, entries: RepoHelmChartList): RepoHelmChartList { + return Object.fromEntries( + iter.filter( + iter.map( + Object.entries(entries), + ([name, charts]) => [ + name, + sortCharts( + charts.map(chart => ({ + ...chart, + created: Date.parse(chart.created).toString(), + repo: repoName, + })), + ), + ] as const + ), + ([, charts]) => !charts.every(chart => chart.deprecated), + ) + ); +}