/** * 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"; class HelmRepoManager { protected repos: HelmRepo[]; 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() { 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 async parseHelmEnv() { const output = await execHelm(["env"]); const lines = output.split(/\r?\n/); // split by new line feed const env: HelmEnv = {}; lines.forEach((line: string) => { const [key, value] = line.split("="); if (key && value) { env[key] = value.replace(/"/g, ""); // strip quotas } }); return env; } public async repo(name: string): Promise { const repos = await this.repositories(); return repos.find((repo) => repo.name === name); } private async list(): Promise { try { const rawConfig = await readFile( this.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 { await this.ensureInitialized(); const repos = await this.list(); return repos.map((repo) => ({ ...repo, cacheFilePath: `${this.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;