mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Replace all uses of promiseExec with promiseExecFile (#4514)
This commit is contained in:
parent
78678bdf2f
commit
e9d99d8485
@ -71,7 +71,8 @@ describe("preferences page tests", () => {
|
|||||||
}
|
}
|
||||||
}, 10*60*1000);
|
}, 10*60*1000);
|
||||||
|
|
||||||
utils.itIf(process.platform !== "win32")("ensures helm repos", async () => {
|
// 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.click("[data-testid=kubernetes-tab]");
|
||||||
await window.waitForSelector("[data-testid=repository-name]", {
|
await window.waitForSelector("[data-testid=repository-name]", {
|
||||||
timeout: 140_000,
|
timeout: 140_000,
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
import { isMac, isWindows } from "./vars";
|
import { isMac, isWindows } from "./vars";
|
||||||
import wincaAPI from "win-ca/api";
|
import wincaAPI from "win-ca/api";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
import { promiseExec } from "./utils/promise-exec";
|
import { promiseExecFile } from "./utils/promise-exec";
|
||||||
|
|
||||||
// DST Root CA X3, which was expired on 9.30.2021
|
// DST Root CA X3, which was expired on 9.30.2021
|
||||||
export const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
|
export const DSTRootCAX3 = "-----BEGIN CERTIFICATE-----\nMIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow\nPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD\nEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O\nrz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq\nOLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b\nxiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw\n7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD\naeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV\nHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG\nSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69\nikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr\nAvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz\nR8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5\nJDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo\nOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ\n-----END CERTIFICATE-----\n";
|
||||||
@ -33,19 +33,25 @@ export function isCertActive(cert: string) {
|
|||||||
return !isExpired;
|
return !isExpired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions
|
||||||
|
const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
|
||||||
|
|
||||||
|
async function execSecurity(...args: string[]): Promise<string[]> {
|
||||||
|
const { stdout } = await promiseExecFile("/usr/bin/security", args);
|
||||||
|
|
||||||
|
return stdout.split(certSplitPattern);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get root CA certificate from MacOSX system keychain
|
* Get root CA certificate from MacOSX system keychain
|
||||||
* Only return non-expred certificates.
|
* Only return non-expred certificates.
|
||||||
*/
|
*/
|
||||||
export async function getMacRootCA() {
|
export async function getMacRootCA() {
|
||||||
// inspired mac-ca https://github.com/jfromaniello/mac-ca
|
// inspired mac-ca https://github.com/jfromaniello/mac-ca
|
||||||
const args = "find-certificate -a -p";
|
const [trusted, rootCA] = await Promise.all([
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions
|
execSecurity("find-certificate", "-a", "-p"),
|
||||||
const splitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
|
execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"),
|
||||||
const systemRootCertsPath = "/System/Library/Keychains/SystemRootCertificates.keychain";
|
]);
|
||||||
const bin = "/usr/bin/security";
|
|
||||||
const trusted = (await promiseExec(`${bin} ${args}`)).stdout.toString().split(splitPattern);
|
|
||||||
const rootCA = (await promiseExec(`${bin} ${args} ${systemRootCertsPath}`)).stdout.toString().split(splitPattern);
|
|
||||||
|
|
||||||
return [...new Set([...trusted, ...rootCA])].filter(isCertActive);
|
return [...new Set([...trusted, ...rootCA])].filter(isCertActive);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as util from "util";
|
import * as util from "util";
|
||||||
import { exec, execFile } from "child_process";
|
import { execFile } from "child_process";
|
||||||
|
|
||||||
export const promiseExec = util.promisify(exec);
|
|
||||||
export const promiseExecFile = util.promisify(execFile);
|
export const promiseExecFile = util.promisify(execFile);
|
||||||
|
|||||||
@ -22,161 +22,229 @@
|
|||||||
import * as tempy from "tempy";
|
import * as tempy from "tempy";
|
||||||
import fse from "fs-extra";
|
import fse from "fs-extra";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import { promiseExec } from "../../common/utils/promise-exec";
|
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||||
import { helmCli } from "./helm-cli";
|
import { helmCli } from "./helm-cli";
|
||||||
import type { Cluster } from "../cluster";
|
|
||||||
import { toCamelCase } from "../../common/utils/camelCase";
|
import { toCamelCase } from "../../common/utils/camelCase";
|
||||||
|
import type { BaseEncodingOptions } from "fs";
|
||||||
|
import { execFile, ExecFileOptions } from "child_process";
|
||||||
|
|
||||||
export async function listReleases(pathToKubeconfig: string, namespace?: string) {
|
async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
|
||||||
const helm = await helmCli.binaryPath();
|
const helmCliPath = await helmCli.binaryPath();
|
||||||
const namespaceFlag = namespace ? `-n ${namespace}` : "--all-namespaces";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { stdout } = await promiseExec(`"${helm}" ls --output json ${namespaceFlag} --kubeconfig ${pathToKubeconfig}`);
|
const { stdout } = await promiseExecFile(helmCliPath, args, options);
|
||||||
const output = JSON.parse(stdout);
|
|
||||||
|
|
||||||
if (output.length == 0) {
|
return stdout;
|
||||||
return output;
|
|
||||||
}
|
|
||||||
output.forEach((release: any, index: number) => {
|
|
||||||
output[index] = toCamelCase(release);
|
|
||||||
});
|
|
||||||
|
|
||||||
return output;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error?.stderr || error;
|
throw error?.stderr || error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
|
||||||
|
const args = [
|
||||||
|
"ls",
|
||||||
|
"--output", "json",
|
||||||
|
];
|
||||||
|
|
||||||
export async function installChart(chart: string, values: any, name: string | undefined, namespace: string, version: string, pathToKubeconfig: string) {
|
if (namespace) {
|
||||||
const helm = await helmCli.binaryPath();
|
args.push("-n", namespace);
|
||||||
const fileName = tempy.file({ name: "values.yaml" });
|
} else {
|
||||||
|
args.push("--all-namespaces");
|
||||||
|
}
|
||||||
|
|
||||||
await fse.writeFile(fileName, yaml.dump(values));
|
args.push("--kubeconfig", pathToKubeconfig);
|
||||||
|
|
||||||
|
const output = JSON.parse(await execHelm(args));
|
||||||
|
|
||||||
|
if (!Array.isArray(output) || output.length == 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.map(toCamelCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function installChart(chart: string, values: any, name: string | undefined = "", namespace: string, version: string, kubeconfigPath: string) {
|
||||||
|
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||||
|
|
||||||
|
await fse.writeFile(valuesFilePath, yaml.dump(values));
|
||||||
|
|
||||||
|
const args = ["install"];
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
args.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
chart,
|
||||||
|
"--version", version,
|
||||||
|
"--values", valuesFilePath,
|
||||||
|
"--namespace", namespace,
|
||||||
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
args.push("--generate-name");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let generateName = "";
|
const output = await execHelm(args);
|
||||||
|
const releaseName = output.split("\n")[0].split(" ")[1].trim();
|
||||||
if (!name) {
|
|
||||||
generateName = "--generate-name";
|
|
||||||
name = "";
|
|
||||||
}
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" install ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} ${generateName}`);
|
|
||||||
const releaseName = stdout.split("\n")[0].split(" ")[1].trim();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
log: stdout,
|
log: output,
|
||||||
release: {
|
release: {
|
||||||
name: releaseName,
|
name: releaseName,
|
||||||
namespace,
|
namespace,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
throw error?.stderr || error;
|
|
||||||
} finally {
|
} finally {
|
||||||
await fse.unlink(fileName);
|
await fse.unlink(valuesFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, cluster: Cluster) {
|
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, kubeconfigPath: string, kubectlPath: string) {
|
||||||
const helm = await helmCli.binaryPath();
|
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||||
const fileName = tempy.file({ name: "values.yaml" });
|
|
||||||
|
|
||||||
await fse.writeFile(fileName, yaml.dump(values));
|
await fse.writeFile(valuesFilePath, yaml.dump(values));
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
"upgrade",
|
||||||
|
name,
|
||||||
|
chart,
|
||||||
|
"--version", version,
|
||||||
|
"--values", valuesFilePath,
|
||||||
|
"--namespace", namespace,
|
||||||
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const output = await execHelm(args);
|
||||||
const { stdout } = await promiseExec(`"${helm}" upgrade ${name} ${chart} --version ${version} -f ${fileName} --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
log: stdout,
|
log: output,
|
||||||
release: getRelease(name, namespace, cluster),
|
release: getRelease(name, namespace, kubeconfigPath, kubectlPath),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
throw error?.stderr || error;
|
|
||||||
} finally {
|
} finally {
|
||||||
await fse.unlink(fileName);
|
await fse.unlink(valuesFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRelease(name: string, namespace: string, cluster: Cluster) {
|
export async function getRelease(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||||
try {
|
const args = [
|
||||||
const helm = await helmCli.binaryPath();
|
"status",
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
name,
|
||||||
|
"--namespace", namespace,
|
||||||
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
"--output", "json",
|
||||||
|
];
|
||||||
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" status ${name} --output json --namespace ${namespace} --kubeconfig ${proxyKubeconfig}`, {
|
const release = JSON.parse(await execHelm(args, {
|
||||||
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
||||||
});
|
}));
|
||||||
const release = JSON.parse(stdout);
|
|
||||||
|
|
||||||
release.resources = await getResources(name, namespace, cluster);
|
release.resources = await getResources(name, namespace, kubeconfigPath, kubectlPath);
|
||||||
|
|
||||||
return release;
|
return release;
|
||||||
} catch (error) {
|
|
||||||
throw error?.stderr || error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRelease(name: string, namespace: string, pathToKubeconfig: string) {
|
export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string) {
|
||||||
try {
|
return execHelm([
|
||||||
const helm = await helmCli.binaryPath();
|
"delete",
|
||||||
const { stdout } = await promiseExec(`"${helm}" delete ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
name,
|
||||||
|
"--namespace", namespace,
|
||||||
return stdout;
|
"--kubeconfig", kubeconfigPath,
|
||||||
} catch (error) {
|
]);
|
||||||
throw error?.stderr || error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetValuesOptions {
|
interface GetValuesOptions {
|
||||||
namespace: string;
|
namespace: string;
|
||||||
all?: boolean;
|
all?: boolean;
|
||||||
pathToKubeconfig: string;
|
kubeconfigPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getValues(name: string, { namespace, all = false, pathToKubeconfig }: GetValuesOptions) {
|
export async function getValues(name: string, { namespace, all = false, kubeconfigPath }: GetValuesOptions) {
|
||||||
try {
|
const args = [
|
||||||
const helm = await helmCli.binaryPath();
|
"get",
|
||||||
const { stdout } = await promiseExec(`"${helm}" get values ${name} ${all ? "--all" : ""} --output yaml --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
"values",
|
||||||
|
name,
|
||||||
|
];
|
||||||
|
|
||||||
return stdout;
|
if (all) {
|
||||||
} catch (error) {
|
args.push("--all");
|
||||||
throw error?.stderr || error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args.push(
|
||||||
|
"--output", "yaml",
|
||||||
|
"--namespace", namespace,
|
||||||
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
return execHelm(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHistory(name: string, namespace: string, pathToKubeconfig: string) {
|
export async function getHistory(name: string, namespace: string, kubeconfigPath: string) {
|
||||||
try {
|
return JSON.parse(await execHelm([
|
||||||
const helm = await helmCli.binaryPath();
|
"history",
|
||||||
const { stdout } = await promiseExec(`"${helm}" history ${name} --output json --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
name,
|
||||||
|
"--output", "json",
|
||||||
return JSON.parse(stdout);
|
"--namespace", namespace,
|
||||||
} catch (error) {
|
"--kubeconfig", kubeconfigPath,
|
||||||
throw error?.stderr || error;
|
]));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rollback(name: string, namespace: string, revision: number, pathToKubeconfig: string) {
|
export async function rollback(name: string, namespace: string, revision: number, kubeconfigPath: string) {
|
||||||
try {
|
return JSON.parse(await execHelm([
|
||||||
const helm = await helmCli.binaryPath();
|
"rollback",
|
||||||
const { stdout } = await promiseExec(`"${helm}" rollback ${name} ${revision} --namespace ${namespace} --kubeconfig ${pathToKubeconfig}`);
|
name,
|
||||||
|
"--namespace", namespace,
|
||||||
return stdout;
|
"--kubeconfig", kubeconfigPath,
|
||||||
} catch (error) {
|
]));
|
||||||
throw error?.stderr || error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getResources(name: string, namespace: string, cluster: Cluster) {
|
async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||||
try {
|
const helmArgs = [
|
||||||
const helm = await helmCli.binaryPath();
|
"get",
|
||||||
const kubectl = await cluster.ensureKubectl();
|
"manifest",
|
||||||
const kubectlPath = await kubectl.getPath();
|
name,
|
||||||
const pathToKubeconfig = await cluster.getProxyKubeconfigPath();
|
"--namespace", namespace,
|
||||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectlPath}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`);
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
];
|
||||||
|
const kubectlArgs = [
|
||||||
|
"get",
|
||||||
|
"--namespace", namespace,
|
||||||
|
"--kubeconfig", kubeconfigPath,
|
||||||
|
"-f", "-",
|
||||||
|
"--output", "json",
|
||||||
|
];
|
||||||
|
|
||||||
return JSON.parse(stdout).items;
|
try {
|
||||||
|
const helmOutput = await execHelm(helmArgs);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
const kubectl = execFile(kubectlPath, kubectlArgs);
|
||||||
|
|
||||||
|
kubectl
|
||||||
|
.on("exit", (code, signal) => {
|
||||||
|
if (typeof code === "number") {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(JSON.parse(stdout).items);
|
||||||
|
} else {
|
||||||
|
reject(stderr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Kubectl exited with signal ${signal}`));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("error", reject);
|
||||||
|
|
||||||
|
kubectl.stderr.on("data", output => stderr += output);
|
||||||
|
kubectl.stdout.on("data", output => stdout += output);
|
||||||
|
kubectl.stdin.write(helmOutput);
|
||||||
|
kubectl.stdin.end();
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,13 +20,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { readFile } from "fs-extra";
|
import { BaseEncodingOptions, readFile } from "fs-extra";
|
||||||
import { promiseExec } from "../../common/utils/promise-exec";
|
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||||
import { helmCli } from "./helm-cli";
|
import { helmCli } from "./helm-cli";
|
||||||
import { Singleton } from "../../common/utils/singleton";
|
import { Singleton } from "../../common/utils/singleton";
|
||||||
import { customRequestPromise } from "../../common/request";
|
import { customRequestPromise } from "../../common/request";
|
||||||
import orderBy from "lodash/orderBy";
|
import orderBy from "lodash/orderBy";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
import type { ExecFileOptions } from "child_process";
|
||||||
|
|
||||||
export type HelmEnv = Record<string, string> & {
|
export type HelmEnv = Record<string, string> & {
|
||||||
HELM_REPOSITORY_CACHE?: string;
|
HELM_REPOSITORY_CACHE?: string;
|
||||||
@ -49,6 +50,18 @@ export interface HelmRepo {
|
|||||||
password?: string,
|
password?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function execHelm(args: string[], options?: BaseEncodingOptions & ExecFileOptions): Promise<string> {
|
||||||
|
const helmCliPath = await helmCli.binaryPath();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await promiseExecFile(helmCliPath, args, options);
|
||||||
|
|
||||||
|
return stdout;
|
||||||
|
} catch (error) {
|
||||||
|
throw error?.stderr || error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class HelmRepoManager extends Singleton {
|
export class HelmRepoManager extends Singleton {
|
||||||
protected repos: HelmRepo[];
|
protected repos: HelmRepo[];
|
||||||
protected helmEnv: HelmEnv;
|
protected helmEnv: HelmEnv;
|
||||||
@ -77,11 +90,8 @@ export class HelmRepoManager extends Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static async parseHelmEnv() {
|
protected static async parseHelmEnv() {
|
||||||
const helm = await helmCli.binaryPath();
|
const output = await execHelm(["env"]);
|
||||||
const { stdout } = await promiseExec(`"${helm}" env`).catch((error) => {
|
const lines = output.split(/\r?\n/); // split by new line feed
|
||||||
throw(error.stderr);
|
|
||||||
});
|
|
||||||
const lines = stdout.split(/\r?\n/); // split by new line feed
|
|
||||||
const env: HelmEnv = {};
|
const env: HelmEnv = {};
|
||||||
|
|
||||||
lines.forEach((line: string) => {
|
lines.forEach((line: string) => {
|
||||||
@ -135,57 +145,73 @@ export class HelmRepoManager extends Singleton {
|
|||||||
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
|
cacheFilePath: `${this.helmEnv.HELM_REPOSITORY_CACHE}/${repo.name}-index.yaml`,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`[HELM]: repositories listing error "${error}"`);
|
logger.error(`[HELM]: repositories listing error`, error);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async update() {
|
public static async update() {
|
||||||
const helm = await helmCli.binaryPath();
|
return execHelm([
|
||||||
const { stdout } = await promiseExec(`"${helm}" repo update`).catch((error) => {
|
"repo",
|
||||||
return { stdout: error.stdout };
|
"update",
|
||||||
});
|
]);
|
||||||
|
|
||||||
return stdout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async addRepo({ name, url }: HelmRepo) {
|
public static async addRepo({ name, url }: HelmRepo) {
|
||||||
logger.info(`[HELM]: adding repo "${name}" from ${url}`);
|
logger.info(`[HELM]: adding repo "${name}" from ${url}`);
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" repo add ${name} ${url}`).catch((error) => {
|
|
||||||
throw(error.stderr);
|
|
||||||
});
|
|
||||||
|
|
||||||
return stdout;
|
return execHelm([
|
||||||
|
"repo",
|
||||||
|
"add",
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async addCustomRepo(repoAttributes : HelmRepo) {
|
public static async addCustomRepo({ name, url, insecureSkipTlsVerify, username, password, caFile, keyFile, certFile }: HelmRepo) {
|
||||||
logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`);
|
logger.info(`[HELM]: adding repo ${name} from ${url}`);
|
||||||
const helm = await helmCli.binaryPath();
|
const args = [
|
||||||
|
"repo",
|
||||||
|
"add",
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
];
|
||||||
|
|
||||||
const insecureSkipTlsVerify = repoAttributes.insecureSkipTlsVerify ? " --insecure-skip-tls-verify" : "";
|
if (insecureSkipTlsVerify) {
|
||||||
const username = repoAttributes.username ? ` --username "${repoAttributes.username}"` : "";
|
args.push("--insecure-skip-tls-verify");
|
||||||
const password = repoAttributes.password ? ` --password "${repoAttributes.password}"` : "";
|
}
|
||||||
const caFile = repoAttributes.caFile ? ` --ca-file "${repoAttributes.caFile}"` : "";
|
|
||||||
const keyFile = repoAttributes.keyFile ? ` --key-file "${repoAttributes.keyFile}"` : "";
|
|
||||||
const certFile = repoAttributes.certFile ? ` --cert-file "${repoAttributes.certFile}"` : "";
|
|
||||||
|
|
||||||
const addRepoCommand = `"${helm}" repo add ${repoAttributes.name} ${repoAttributes.url}${insecureSkipTlsVerify}${username}${password}${caFile}${keyFile}${certFile}`;
|
if (username) {
|
||||||
const { stdout } = await promiseExec(addRepoCommand).catch((error) => {
|
args.push("--username", username);
|
||||||
throw(error.stderr);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return stdout;
|
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 static async removeRepo({ name, url }: HelmRepo): Promise<string> {
|
public static async removeRepo({ name, url }: HelmRepo): Promise<string> {
|
||||||
logger.info(`[HELM]: removing repo "${name}" from ${url}`);
|
logger.info(`[HELM]: removing repo ${name} (${url})`);
|
||||||
const helm = await helmCli.binaryPath();
|
|
||||||
const { stdout } = await promiseExec(`"${helm}" repo remove ${name}`).catch((error) => {
|
|
||||||
throw(error.stderr);
|
|
||||||
});
|
|
||||||
|
|
||||||
return stdout;
|
return execHelm([
|
||||||
|
"repo",
|
||||||
|
"remove",
|
||||||
|
name,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,13 +65,19 @@ class HelmService {
|
|||||||
public async listReleases(cluster: Cluster, namespace: string = null) {
|
public async listReleases(cluster: Cluster, namespace: string = null) {
|
||||||
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
|
||||||
|
logger.debug("list releases");
|
||||||
|
|
||||||
return listReleases(proxyKubeconfig, namespace);
|
return listReleases(proxyKubeconfig, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
public async getRelease(cluster: Cluster, releaseName: string, namespace: string) {
|
||||||
|
const kubeconfigPath = await cluster.getProxyKubeconfigPath();
|
||||||
|
const kubectl = await cluster.ensureKubectl();
|
||||||
|
const kubectlPath = await kubectl.getPath();
|
||||||
|
|
||||||
logger.debug("Fetch release");
|
logger.debug("Fetch release");
|
||||||
|
|
||||||
return getRelease(releaseName, namespace, cluster);
|
return getRelease(releaseName, namespace, kubeconfigPath, kubectlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseValues(releaseName: string, { cluster, namespace, all }: GetReleaseValuesArgs) {
|
public async getReleaseValues(releaseName: string, { cluster, namespace, all }: GetReleaseValuesArgs) {
|
||||||
@ -79,7 +85,7 @@ class HelmService {
|
|||||||
|
|
||||||
logger.debug("Fetch release values");
|
logger.debug("Fetch release values");
|
||||||
|
|
||||||
return getValues(releaseName, { namespace, all, pathToKubeconfig });
|
return getValues(releaseName, { namespace, all, kubeconfigPath: pathToKubeconfig });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
public async getReleaseHistory(cluster: Cluster, releaseName: string, namespace: string) {
|
||||||
@ -99,9 +105,13 @@ class HelmService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) {
|
public async updateRelease(cluster: Cluster, releaseName: string, namespace: string, data: { chart: string; values: {}; version: string }) {
|
||||||
|
const proxyKubeconfig = await cluster.getProxyKubeconfigPath();
|
||||||
|
const kubectl = await cluster.ensureKubectl();
|
||||||
|
const kubectlPath = await kubectl.getPath();
|
||||||
|
|
||||||
logger.debug("Upgrade release");
|
logger.debug("Upgrade release");
|
||||||
|
|
||||||
return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, cluster);
|
return upgradeRelease(releaseName, data.chart, data.values, namespace, data.version, proxyKubeconfig, kubectlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
public async rollback(cluster: Cluster, releaseName: string, namespace: string, revision: number) {
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { promiseExec } from "../common/utils/promise-exec";
|
import { promiseExecFile } from "../common/utils/promise-exec";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
import { ensureDir, pathExists } from "fs-extra";
|
import { ensureDir, pathExists } from "fs-extra";
|
||||||
import * as lockFile from "proper-lockfile";
|
import * as lockFile from "proper-lockfile";
|
||||||
@ -199,7 +199,12 @@ export class Kubectl {
|
|||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await promiseExec(`"${path}" version --client=true -o json`);
|
const args = [
|
||||||
|
"version",
|
||||||
|
"--client", "true",
|
||||||
|
"--output", "json",
|
||||||
|
];
|
||||||
|
const { stdout } = await promiseExecFile(path, args);
|
||||||
const output = JSON.parse(stdout);
|
const output = JSON.parse(stdout);
|
||||||
|
|
||||||
if (!checkVersion) {
|
if (!checkVersion) {
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
<div className="flex gaps align-center">
|
<div className="flex gaps align-center">
|
||||||
<Input
|
<Input
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
validators = {isPath}
|
validators={isPath}
|
||||||
className="box grow"
|
className="box grow"
|
||||||
value={this.getFilePath(fileType)}
|
value={this.getFilePath(fileType)}
|
||||||
onChange={v => this.setFilepath(fileType, v)}
|
onChange={v => this.setFilepath(fileType, v)}
|
||||||
@ -172,7 +172,7 @@ export class AddHelmRepoDialog extends React.Component<Props> {
|
|||||||
close={this.close}
|
close={this.close}
|
||||||
>
|
>
|
||||||
<Wizard header={header} done={this.close}>
|
<Wizard header={header} done={this.close}>
|
||||||
<WizardStep contentClass="flow column" nextLabel="Add" next={()=>{this.addCustomRepo();}}>
|
<WizardStep contentClass="flow column" nextLabel="Add" next={() => this.addCustomRepo()}>
|
||||||
<div className="flex column gaps">
|
<div className="flex column gaps">
|
||||||
<Input
|
<Input
|
||||||
autoFocus required
|
autoFocus required
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user