/** * 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 yaml from "js-yaml"; import { readFile } from "fs-extra"; import { customRequestPromise } from "../../common/request"; import orderBy from "lodash/orderBy"; import logger from "../logger"; import { execHelm } from "./exec"; import type { HelmEnv, HelmRepo, HelmRepoConfig } from "./helm-repo-manager"; interface EnsuredHelmRepoManagerData { helmEnv: HelmEnv; didUpdateOnce: boolean; } export class HelmRepoManager { protected helmEnv?: HelmEnv; protected didUpdateOnce?: boolean; public async loadAvailableRepos(): Promise { const res = await customRequestPromise({ uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json", json: true, resolveWithFullResponse: true, timeout: 10000, }); return orderBy(res.body as HelmRepo[], repo => repo.name); } private async ensureInitialized(): Promise { this.helmEnv ??= await this.parseHelmEnv(); const repos = await this.list(this.helmEnv); if (repos.length === 0) { await this.addRepo({ name: "bitnami", url: "https://charts.bitnami.com/bitnami", }); } if (!this.didUpdateOnce) { await this.update(); this.didUpdateOnce = true; } return { didUpdateOnce: this.didUpdateOnce, helmEnv: this.helmEnv, }; } protected async parseHelmEnv() { const output = await execHelm(["env"]); const lines = output.split(/\r?\n/); // split by new line feed const env: Partial> = {}; lines.forEach((line: string) => { const [key, value] = line.split("="); if (key && value) { env[key] = value.replace(/"/g, ""); // strip quotas } }); return env as HelmEnv; } public async repo(name: string): Promise { const repos = await this.repositories(); return repos.find(repo => repo.name === name); } private async list(helmEnv: HelmEnv): Promise { try { const rawConfig = await readFile(helmEnv.HELM_REPOSITORY_CONFIG, "utf8"); const parsedConfig = yaml.load(rawConfig) as HelmRepoConfig; if (typeof parsedConfig === "object" && parsedConfig) { return parsedConfig.repositories; } } catch { // ignore error } return []; } public async repositories(): Promise { try { const { helmEnv } = await this.ensureInitialized(); const repos = await this.list(helmEnv); return repos.map(repo => ({ ...repo, cacheFilePath: `${helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`, })); } catch (error) { logger.error(`[HELM]: repositories listing error`, error); return []; } } public async update() { return execHelm([ "repo", "update", ]); } public async addRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) { logger.info(`[HELM]: adding repo ${name} from ${url}`); const args = [ "repo", "add", name, url, ]; if (insecureSkipTlsVerify) { args.push("--insecure-skip-tls-verify"); } if (username) { args.push("--username", username); } if (password) { args.push("--password", password); } if (caFile) { args.push("--ca-file", caFile); } if (keyFile) { args.push("--key-file", keyFile); } if (certFile) { args.push("--cert-file", certFile); } return execHelm(args); } public async removeRepo({ name, url }: HelmRepo): Promise { logger.info(`[HELM]: removing repo ${name} (${url})`); return execHelm([ "repo", "remove", name, ]); } } const helmRepoManagerInjectable = getInjectable({ id: "helm-repo-manager", instantiate: () => new HelmRepoManager(), }); export default helmRepoManagerInjectable;