From ed91ef2d0377d13ee9aee458180f732f8c05eeea Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Wed, 16 Feb 2022 14:43:26 -0500 Subject: [PATCH] Cleanup HelmRepoManager and (#4564) --- .../__tests__/app-preferences.tests.ts | 4 +- src/main/helm/helm-release-manager.ts | 4 +- src/main/helm/helm-repo-manager.ts | 69 ++++++++----------- .../+preferences/add-helm-repo-dialog.tsx | 31 +++++---- .../components/+preferences/helm-charts.tsx | 57 ++++++++------- 5 files changed, 81 insertions(+), 84 deletions(-) diff --git a/integration/__tests__/app-preferences.tests.ts b/integration/__tests__/app-preferences.tests.ts index d342106631..32b89bbdf3 100644 --- a/integration/__tests__/app-preferences.tests.ts +++ b/integration/__tests__/app-preferences.tests.ts @@ -60,9 +60,7 @@ describe("preferences page tests", () => { // Skipping, but will turn it on again in the follow up PR it.skip("ensures helm repos", async () => { await window.click("[data-testid=kubernetes-tab]"); - await window.waitForSelector("[data-testid=repository-name]", { - timeout: 140_000, - }); + await window.waitForSelector("[data-testid=repository-name]"); await window.click("#HelmRepoSelect"); await window.waitForSelector("div.Select__option"); }, 10*60*1000); diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 9c1d78bcfc..72704eb020 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -104,10 +104,8 @@ export async function upgradeRelease(name: string, chart: string, values: any, n ]; try { - const output = await execHelm(args); - return { - log: output, + log: await execHelm(args), release: getRelease(name, namespace, kubeconfigPath, kubectlPath), }; } finally { diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 0130c7a1cd..3674654a29 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -49,9 +49,9 @@ async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFile export class HelmRepoManager extends Singleton { protected repos: HelmRepo[]; protected helmEnv: HelmEnv; - protected initialized: boolean; + protected didUpdateOnce: boolean; - public static async loadAvailableRepos(): Promise { + public async loadAvailableRepos(): Promise { const res = await customRequestPromise({ uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json", json: true, @@ -59,21 +59,31 @@ export class HelmRepoManager extends Singleton { timeout: 10000, }); - return orderBy(res.body, repo => repo.name); + return orderBy(res.body as HelmRepo[], repo => repo.name); } - private async init() { + private async ensureInitialized() { helmCli.setLogger(logger); await helmCli.ensureBinary(); - if (!this.initialized) { - this.helmEnv = await HelmRepoManager.parseHelmEnv(); - await HelmRepoManager.update(); - this.initialized = true; + this.helmEnv ??= await this.parseHelmEnv(); + + const repos = await this.list(); + + if (repos.length === 0) { + await this.addRepo({ + name: "bitnami", + url: "https://charts.bitnami.com/bitnami", + }); + } + + if (!this.didUpdateOnce) { + await this.update(); + this.didUpdateOnce = true; } } - protected static async parseHelmEnv() { + protected async parseHelmEnv() { const output = await execHelm(["env"]); const lines = output.split(/\r?\n/); // split by new line feed const env: HelmEnv = {}; @@ -95,38 +105,28 @@ export class HelmRepoManager extends Singleton { return repos.find(repo => repo.name === name); } - private async readConfig(): Promise { + private async list(): Promise { try { const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8"); - const parsedConfig = yaml.load(rawConfig); + const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig; if (typeof parsedConfig === "object" && parsedConfig) { - return parsedConfig as HelmRepoConfig; + return parsedConfig.repositories; } } catch { // ignore error } - return { - repositories: [], - }; + return []; } public async repositories(): Promise { try { - if (!this.initialized) { - await this.init(); - } + await this.ensureInitialized(); - const { repositories } = await this.readConfig(); + const repos = await this.list(); - if (!repositories.length) { - await HelmRepoManager.addRepo({ name: "bitnami", url: "https://charts.bitnami.com/bitnami" }); - - return await this.repositories(); - } - - return repositories.map(repo => ({ + return repos.map(repo => ({ ...repo, cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`, })); @@ -137,25 +137,14 @@ export class HelmRepoManager extends Singleton { } } - public static async update() { + public async update() { return execHelm([ "repo", "update", ]); } - public static async addRepo({ name, url }: HelmRepo) { - logger.info(`[HELM]: adding repo "${name}" from ${url}`); - - return execHelm([ - "repo", - "add", - name, - url, - ]); - } - - public static async addCustomRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) { + public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) { logger.info(`[HELM]: adding repo ${name} from ${url}`); const args = [ "repo", @@ -191,7 +180,7 @@ export class HelmRepoManager extends Singleton { return execHelm(args); } - public static async removeRepo({ name, url }: HelmRepo): Promise { + public async removeRepo({ name, url }: HelmRepo): Promise { logger.info(`[HELM]: removing repo ${name} (${url})`); return execHelm([ diff --git a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx index d8bec1ffcf..0d70ea4ab2 100644 --- a/src/renderer/components/+preferences/add-helm-repo-dialog.tsx +++ b/src/renderer/components/+preferences/add-helm-repo-dialog.tsx @@ -7,7 +7,7 @@ import "./add-helm-repo-dialog.scss"; import React from "react"; import type { FileFilter } from "electron"; -import { observable, makeObservable } from "mobx"; +import { observable, makeObservable, action } from "mobx"; import { observer } from "mobx-react"; import { Dialog, DialogProps } from "../dialog"; import { Wizard, WizardStep } from "../wizard"; @@ -35,10 +35,12 @@ const dialogState = observable.object({ isOpen: false, }); +function getEmptyRepo(): HelmRepo { + return { name: "", url: "", username: "", password: "", insecureSkipTlsVerify: false, caFile: "", keyFile: "", certFile: "" }; +} + @observer export class AddHelmRepoDialog extends React.Component { - private emptyRepo = { name: "", url: "", username: "", password: "", insecureSkipTlsVerify: false, caFile:"", keyFile: "", certFile: "" }; - private static keyExtensions = ["key", "keystore", "jks", "p12", "pfx", "pem"]; private static certExtensions = ["crt", "cer", "ca-bundle", "p7b", "p7c", "p7s", "p12", "pfx", "pem"]; @@ -55,14 +57,15 @@ export class AddHelmRepoDialog extends React.Component { dialogState.isOpen = false; } - @observable helmRepo : HelmRepo = this.emptyRepo; + @observable helmRepo = getEmptyRepo(); @observable showOptions = false; - close = () => { - AddHelmRepoDialog.close(); - this.helmRepo = this.emptyRepo; - this.showOptions = false; - }; + @action + close = () => { + AddHelmRepoDialog.close(); + this.helmRepo = getEmptyRepo(); + this.showOptions = false; + }; setFilepath(type: FileType, value: string) { this.helmRepo[type] = value; @@ -91,8 +94,8 @@ export class AddHelmRepoDialog extends React.Component { async addCustomRepo() { try { - await HelmRepoManager.addCustomRepo(this.helmRepo); - Notifications.ok(<>Helm repository {this.helmRepo.name} has added); + await HelmRepoManager.getInstance().addRepo(this.helmRepo); + Notifications.ok(<>Helm repository {this.helmRepo.name} has been added); this.props.onAddRepo(); this.close(); } catch (err) { @@ -127,9 +130,9 @@ export class AddHelmRepoDialog extends React.Component { value={this.helmRepo.insecureSkipTlsVerify} onChange={v => this.helmRepo.insecureSkipTlsVerify = v} /> - {this.renderFileInput(`Key file`, FileType.KeyFile, AddHelmRepoDialog.keyExtensions)} - {this.renderFileInput(`Ca file`, FileType.CaFile, AddHelmRepoDialog.certExtensions)} - {this.renderFileInput(`Certificate file`, FileType.CertFile, AddHelmRepoDialog.certExtensions)} + {this.renderFileInput("Key file", FileType.KeyFile, AddHelmRepoDialog.keyExtensions)} + {this.renderFileInput("Ca file", FileType.CaFile, AddHelmRepoDialog.certExtensions)} + {this.renderFileInput("Certificate file", FileType.CertFile, AddHelmRepoDialog.certExtensions)} (); @@ -37,32 +39,42 @@ export class HelmCharts extends React.Component { })); } - async componentDidMount() { - await this.loadRepos(); + componentDidMount() { + this.loadAvailableRepos().catch(noop); + this.loadRepos().catch(noop); } - @action - async loadRepos() { - this.loading = true; + async loadAvailableRepos() { + this.loadingAvailableRepos = true; try { if (!this.repos.length) { - this.repos = await HelmRepoManager.loadAvailableRepos(); + this.repos = await HelmRepoManager.getInstance().loadAvailableRepos(); } - const repos = await HelmRepoManager.getInstance().repositories(); // via helm-cli - - this.addedRepos.clear(); - repos.forEach(repo => this.addedRepos.set(repo.name, repo)); } catch (err) { Notifications.error(String(err)); } - this.loading = false; + this.loadingAvailableRepos = false; + } + + async loadRepos() { + this.loadingRepos = true; + + try { + const repos = await HelmRepoManager.getInstance().repositories(); // via helm-cli + + this.addedRepos.replace(repos.map(repo => [repo.name, repo])); + } catch (err) { + Notifications.error(String(err)); + } + + this.loadingRepos = false; } async addRepo(repo: HelmRepo) { try { - await HelmRepoManager.addRepo(repo); + await HelmRepoManager.getInstance().addRepo(repo); this.addedRepos.set(repo.name, repo); } catch (err) { Notifications.error(<>Adding helm branch {repo.name} has failed: {String(err)}); @@ -71,7 +83,7 @@ export class HelmCharts extends React.Component { async removeRepo(repo: HelmRepo) { try { - await HelmRepoManager.removeRepo(repo); + await HelmRepoManager.getInstance().removeRepo(repo); this.addedRepos.delete(repo.name); } catch (err) { Notifications.error( @@ -80,17 +92,14 @@ export class HelmCharts extends React.Component { } } - onRepoSelect = async ({ value: repo }: SelectOption) => { + onRepoSelect = async ({ value: repo }: SelectOption): Promise => { const isAdded = this.addedRepos.has(repo.name); if (isAdded) { - Notifications.ok(<>Helm branch {repo.name} already in use); - - return; + return void Notifications.ok(<>Helm branch {repo.name} already in use); } - this.loading = true; + await this.addRepo(repo); - this.loading = false; }; formatOptionLabel = ({ value: repo }: SelectOption) => { @@ -107,7 +116,7 @@ export class HelmCharts extends React.Component { renderRepositories() { const repos = Array.from(this.addedRepos); - if (this.loading) { + if (this.loadingRepos) { return
; } @@ -137,8 +146,8 @@ export class HelmCharts extends React.Component {