1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

remove depricated package request from our code

Signed-off-by: Sebastian Malton <smalton@mirantis.com>
This commit is contained in:
Sebastian Malton 2020-09-11 13:36:32 -04:00 committed by Sebastian Malton
parent 33c405bdcf
commit 47fbf629ea
19 changed files with 397 additions and 269 deletions

View File

@ -1,10 +1,16 @@
import packageInfo from "../package.json";
import fs from "fs";
import request from "request";
import fse from "fs-extra";
import got from "got/dist/source";
import md5File from "md5-file";
import requestPromise from "request-promise-native";
import { ensureDir, pathExists } from "fs-extra";
import path from "path";
import stream from "stream";
import util, { promisify } from "util";
import packageInfo from "../package.json";
import { GotStreamFunctionOptions } from "../src/common/got-opts";
import logger from "../src/main/logger";
const pipeline = promisify(stream.pipeline);
class KubectlDownloader {
public kubectlVersion: string;
@ -22,21 +28,26 @@ class KubectlDownloader {
}
protected async urlEtag() {
const response = await requestPromise({
method: "HEAD",
uri: this.url,
resolveWithFullResponse: true
}).catch((error) => { console.log(error); });
try {
const res = await got.head(this.url);
const { etag } = res.headers;
if (response.headers["etag"]) {
return response.headers["etag"].replace(/"/g, "");
if (Array.isArray(etag)) {
return etag[0].replace(/"/g, "");
}
if (typeof etag === "string") {
return etag.replace(/"/g, "");
}
} catch (err) {
logger.error("Failed to get etag:", err);
}
return "";
}
public async checkBinary() {
const exists = await pathExists(this.path);
const exists = await fse.pathExists(this.path);
if (exists) {
const hash = md5File.sync(this.path);
@ -55,65 +66,57 @@ class KubectlDownloader {
return false;
}
public async downloadKubectl() {
public async downloadKubectl(): Promise<void> {
const exists = await this.checkBinary();
if(exists) {
console.log("Already exists and is valid");
return;
if (exists) {
return void console.log("Already exists and is valid");
}
await ensureDir(path.dirname(this.path), 0o755);
const file = fs.createWriteStream(this.path);
await fse.ensureDir(path.dirname(this.path), 0o755);
console.log(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
const requestOpts: request.UriOptions & request.CoreOptions = {
uri: this.url,
gzip: true
const options: GotStreamFunctionOptions = {
decompress: true,
isStream: true,
};
const stream = request(requestOpts);
stream.on("complete", () => {
console.log("kubectl binary download finished");
// eslint-disable-next-line @typescript-eslint/no-empty-function
file.end(() => {});
});
stream.on("error", (error) => {
console.log(error);
// eslint-disable-next-line @typescript-eslint/no-empty-function
fs.unlink(this.path, () => {});
throw(error);
});
return new Promise((resolve, reject) => {
file.on("close", () => {
console.log("kubectl binary download closed");
fs.chmod(this.path, 0o755, (err) => {
if (err) reject(err);
});
resolve();
});
stream.pipe(file);
});
try {
// TODO: improve the UI for this to use some sort of loading bar TUI
await pipeline(
got.stream(this.url, options),
fs.createWriteStream(this.path)
);
await fse.chmod(this.path, 0o755);
} catch (err) {
logger.error("Failed to download kubectl:", err);
}
}
}
const downloadVersion = packageInfo.config.bundledKubectlVersion;
const baseDir = path.join(process.env.INIT_CWD, "binaries", "client");
const downloads = [
interface DownloadTarget {
platform: string;
target: string;
arch: string;
}
const downloads: DownloadTarget[] = [
{ platform: "linux", arch: "amd64", target: path.join(baseDir, "linux", "x64", "kubectl") },
{ platform: "darwin", arch: "amd64", target: path.join(baseDir, "darwin", "x64", "kubectl") },
{ platform: "windows", arch: "amd64", target: path.join(baseDir, "windows", "x64", "kubectl.exe") },
{ platform: "windows", arch: "386", target: path.join(baseDir, "windows", "ia32", "kubectl.exe") }
];
downloads.forEach((dlOpts) => {
console.log(dlOpts);
const downloader = new KubectlDownloader(downloadVersion, dlOpts.platform, dlOpts.arch, dlOpts.target);
async function downloadOne(opts: DownloadTarget) {
const downloader = new KubectlDownloader(downloadVersion, opts.platform, opts.arch, opts.target);
console.log(`Downloading: ${JSON.stringify(dlOpts)}`);
downloader.downloadKubectl().then(() => downloader.checkBinary().then(() => console.log("Download complete")));
});
console.log(`Downloading: ${util.inspect(opts, false, null, true)}`);
await downloader.downloadKubectl();
await downloader.checkBinary();
console.log(`Finished downloading for ${opts.platform}/${opts.arch}`);
}
Promise.all(downloads.map(downloadOne));

View File

@ -13,7 +13,7 @@ import * as utils from "../helpers/utils";
import { listHelmRepositories } from "../helpers/utils";
import { fail } from "assert";
jest.setTimeout(60000);
jest.setTimeout(120000);
// FIXME (!): improve / simplify all css-selectors + use [data-test-id="some-id"] (already used in some tests below)
describe("Lens integration tests", () => {

View File

@ -49,12 +49,13 @@ export function describeIf(condition: boolean) {
return condition ? describe : describe.skip;
}
export function setup(): Application {
export function setup(waitTimeout = 60000): Application {
return new Application({
path: AppPaths[process.platform], // path to electron app
args: [],
startTimeout: 30000,
waitTimeout: 60000,
waitTimeout,
chromeDriverArgs: ["remote-debugging-port=9222"],
env: {
CICD: "true"
}
@ -101,7 +102,7 @@ export async function tearDown(app: Application) {
await app.stop();
try {
process.kill(pid, "SIGKILL");
process.kill(pid, 0);
} catch (e) {
console.error(e);
}

View File

@ -207,7 +207,9 @@
"electron-window-state": "^5.0.3",
"filenamify": "^4.1.0",
"fs-extra": "^9.0.1",
"got": "^11.8.1",
"handlebars": "^4.7.6",
"hpagent": "^0.1.1",
"http-proxy": "^1.18.1",
"immer": "^8.0.1",
"js-yaml": "^3.14.0",

31
src/common/got-opts.ts Normal file
View File

@ -0,0 +1,31 @@
import { Options } from "got/dist/source";
import { HttpsProxyAgent } from "hpagent";
import { userStore } from "./user-store";
declare type Except<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
declare type Merge<FirstType, SecondType> = Except<FirstType, Extract<keyof FirstType, keyof SecondType>> & SecondType;
export type GotStreamFunctionOptions = Merge<Options, {
isStream?: true;
}>;
export function defaultGotOptions(): Options {
const { httpsProxy, allowUntrustedCAs } = userStore.preferences;
const rejectUnauthorized = !allowUntrustedCAs;
if (!httpsProxy) {
return { rejectUnauthorized };
}
const https = new HttpsProxyAgent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
proxy: httpsProxy,
});
return {
agent: { https },
rejectUnauthorized,
};
}

View File

@ -1,29 +0,0 @@
import request from "request";
import requestPromise from "request-promise-native";
import { userStore } from "./user-store";
// todo: get rid of "request" (deprecated)
// https://github.com/lensapp/lens/issues/459
function getDefaultRequestOpts(): Partial<request.Options> {
const { httpsProxy, allowUntrustedCAs } = userStore.preferences;
return {
proxy: httpsProxy || undefined,
rejectUnauthorized: !allowUntrustedCAs,
};
}
/**
* @deprecated
*/
export function customRequest(opts: request.Options) {
return request.defaults(getDefaultRequestOpts())(opts);
}
/**
* @deprecated
*/
export function customRequestPromise(opts: requestPromise.Options) {
return requestPromise.defaults(getDefaultRequestOpts())(opts);
}

View File

@ -26,7 +26,7 @@ export class BaseClusterDetector {
timeout: 30000,
...options,
headers: {
Host: `${this.cluster.id}.${new URL(this.cluster.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
Host: `${this.cluster.id}.${this.cluster.kubeProxyUrl.host}`, // required in ClusterManager.getClusterForRequest()
...(options.headers || {}),
},
});

View File

@ -1,6 +1,5 @@
import { ipcMain } from "electron";
import type { ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences } from "../common/cluster-store";
import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api";
import type { WorkspaceId } from "../common/workspace-store";
import { action, comparer, computed, observable, reaction, toJS, when } from "mobx";
import { apiKubePrefix } from "../common/vars";
@ -10,12 +9,13 @@ import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttribu
import { Kubectl } from "./kubectl";
import { KubeconfigManager } from "./kubeconfig-manager";
import { loadConfig, validateKubeConfig } from "../common/kube-helpers";
import request, { RequestPromiseOptions } from "request-promise-native";
import { apiResources, KubeApiResource } from "../common/rbac";
import logger from "./logger";
import { VersionDetector } from "./cluster-detectors/version-detector";
import { detectorRegistry } from "./cluster-detectors/detector-registry";
import plimit from "p-limit";
import got, { OptionsOfJSONResponseBody } from "got";
import { URL } from "url";
export enum ClusterStatus {
AccessGranted = 2,
@ -86,7 +86,7 @@ export class Cluster implements ClusterModel, ClusterState {
whenReady = when(() => this.ready);
/**
* Is cluster object initializinng on-going
* Is cluster object initializing on-going
*
* @observable
*/
@ -128,7 +128,7 @@ export class Cluster implements ClusterModel, ClusterState {
* @observable
* @internal
*/
@observable kubeProxyUrl: string; // lens-proxy to kube-api url
@observable kubeProxyUrl: URL; // lens-proxy to kube-api url
/**
* Is cluster instance enabled (disabled clusters are currently hidden)
*
@ -298,7 +298,7 @@ export class Cluster implements ClusterModel, ClusterState {
this.initializing = true;
this.contextHandler = new ContextHandler(this);
this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port);
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
this.kubeProxyUrl = new URL(`http://localhost:${port}${apiKubePrefix}`);
this.initialized = true;
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
id: this.id,
@ -495,31 +495,31 @@ export class Cluster implements ClusterModel, ClusterState {
return this.kubeconfigManager.getPath();
}
protected async k8sRequest<T = any>(path: string, options: RequestPromiseOptions = {}): Promise<T> {
options.headers ??= {};
options.json ??= true;
options.timeout ??= 30000;
options.headers.Host = `${this.id}.${new URL(this.kubeProxyUrl).host}`; // required in ClusterManager.getClusterForRequest()
private getUrlTo(path: string): URL {
const url = new URL(this.kubeProxyUrl.toString());
return request(this.kubeProxyUrl + path, options);
url.pathname += path;
return url;
}
/**
*
* @param prometheusPath path to prometheus service
* @param queryParams query parameters
* @internal
*/
getMetrics(prometheusPath: string, queryParams: IMetricsReqParams & { query: string }) {
protected k8sRequest<T = any>(url: URL, options: OptionsOfJSONResponseBody = {}): Promise<T> {
options.timeout ??= 5000;
options.headers ??= {};
options.headers["content-type"] ??= "application/json";
options.headers.host ??= `${this.id}.${this.kubeProxyUrl.host}`; // required in ClusterManager.getClusterForRequest()
return got<T>(url, options).json();
}
getMetrics(prometheusPath: string, searchParams: Record<string, any>) {
const prometheusPrefix = this.preferences.prometheus?.prefix || "";
const metricsPath = `/api/v1/namespaces/${prometheusPath}/proxy${prometheusPrefix}/api/v1/query_range`;
return this.k8sRequest(metricsPath, {
return this.k8sRequest(this.getUrlTo(metricsPath), {
timeout: 0,
resolveWithFullResponse: false,
json: true,
method: "POST",
form: queryParams,
form: searchParams,
});
}

View File

@ -1,4 +1,4 @@
import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry";
import type { PrometheusService } from "./prometheus/provider-registry";
import type { ClusterPrometheusPreferences } from "../common/cluster-store";
import type { Cluster } from "./cluster";
import type httpProxy from "http-proxy";
@ -58,14 +58,14 @@ export class ContextHandler {
async getPrometheusService(): Promise<PrometheusService> {
const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders;
const prometheusPromises: Promise<PrometheusService>[] = providers.map(async (provider: PrometheusProvider): Promise<PrometheusService> => {
const apiClient = (await this.cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
return await provider.getPrometheusService(apiClient);
});
const prometheusPromises = providers.map(provider => (
this.cluster.getProxyKubeconfig()
.then(kubeConfig => kubeConfig.makeApiClient(CoreV1Api))
.then(apiClient => provider.getPrometheusService(apiClient))
));
const resolvedPrometheusServices = await Promise.all(prometheusPromises);
return resolvedPrometheusServices.filter(n => n)[0];
return resolvedPrometheusServices.find(n => n);
}
async getPrometheusPath(): Promise<string> {

View File

@ -1,11 +1,13 @@
import yaml from "js-yaml";
import { readFile } from "fs-extra";
import { promiseExec } from "../promise-exec";
import { promiseExec, stdOptimizedPromiseExec } from "../promise-exec";
import { helmCli } from "./helm-cli";
import { Singleton } from "../../common/utils/singleton";
import { customRequestPromise } from "../../common/request";
import { defaultGotOptions } from "../../common/got-opts";
import orderBy from "lodash/orderBy";
import logger from "../logger";
import got, { OptionsOfJSONResponseBody } from "got";
import { filter } from "lodash";
export type HelmEnv = Record<string, string> & {
HELM_REPOSITORY_CACHE?: string;
@ -36,14 +38,13 @@ export class HelmRepoManager extends Singleton {
protected initialized: boolean;
async loadAvailableRepos(): Promise<HelmRepo[]> {
const res = await customRequestPromise({
uri: "https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json",
json: true,
resolveWithFullResponse: true,
timeout: 10000,
});
const options = {
...defaultGotOptions() as OptionsOfJSONResponseBody,
timeout: 10000
};
const r = await got<HelmRepo[]>("https://github.com/lensapp/artifact-hub-repositories/releases/download/latest/repositories.json", options);
return orderBy<HelmRepo>(res.body, repo => repo.name);
return orderBy(r.body, repo => repo.name);
}
async init() {
@ -60,7 +61,7 @@ export class HelmRepoManager extends Singleton {
protected async parseHelmEnv() {
const helm = await helmCli.binaryPath();
const { stdout } = await promiseExec(`"${helm}" env`).catch((error) => {
throw(error.stderr);
throw (error.stderr);
});
const lines = stdout.split(/\r?\n/); // split by new line feed
const env: HelmEnv = {};
@ -114,50 +115,41 @@ export class HelmRepoManager extends Singleton {
public async update() {
const helm = await helmCli.binaryPath();
const { stdout } = await promiseExec(`"${helm}" repo update`).catch((error) => {
return { stdout: error.stdout };
});
const command = `"${helm}" repo update`;
return stdout;
return stdOptimizedPromiseExec(command);
}
public async addRepo({ name, url }: HelmRepo) {
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);
});
const command = `"${helm}" repo add ${name} ${url}`;
return stdout;
return stdOptimizedPromiseExec(command);
}
public async addСustomRepo(repoAttributes : HelmRepo) {
logger.info(`[HELM]: adding repo "${repoAttributes.name}" from ${repoAttributes.url}`);
const helm = await helmCli.binaryPath();
const args = filter([
repoAttributes.insecureSkipTlsVerify && "--insecure-skip-tls-verify",
repoAttributes.username && `--username "${repoAttributes.username}"`,
repoAttributes.username && `--username "${repoAttributes.username}"`,
repoAttributes.caFile && `--ca-file "${repoAttributes.caFile}"`,
repoAttributes.keyFile && `--key-file "${repoAttributes.keyFile}"`,
repoAttributes.certFile && `--cert-file "${repoAttributes.certFile}"`,
]);
const command = `"${helm}" repo add ${repoAttributes.name} ${repoAttributes.url} ${args.join(" ")}`;
const insecureSkipTlsVerify = repoAttributes.insecureSkipTlsVerify ? " --insecure-skip-tls-verify" : "";
const username = repoAttributes.username ? ` --username "${repoAttributes.username}"` : "";
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}`;
const { stdout } = await promiseExec(addRepoCommand).catch((error) => {
throw(error.stderr);
});
return stdout;
return stdOptimizedPromiseExec(command);
}
public async removeRepo({ name, url }: HelmRepo): Promise<string> {
logger.info(`[HELM]: removing repo "${name}" from ${url}`);
const helm = await helmCli.binaryPath();
const { stdout } = await promiseExec(`"${helm}" repo remove ${name}`).catch((error) => {
throw(error.stderr);
});
const command = `"${helm}" repo remove ${name}`;
return stdout;
return stdOptimizedPromiseExec(command);
}
}

View File

@ -1,15 +1,21 @@
import { app, remote } from "electron";
import path from "path";
import fs from "fs";
import { promiseExec } from "./promise-exec";
import logger from "./logger";
import { ensureDir, pathExists } from "fs-extra";
import got from "got";
import path from "path";
import * as lockFile from "proper-lockfile";
import { helmCli } from "./helm/helm-cli";
import stream from "stream";
import { promisify } from "util";
import { defaultGotOptions, GotStreamFunctionOptions } from "../common/got-opts";
import { userStore } from "../common/user-store";
import { customRequest } from "../common/request";
import { getBundledKubectlVersion } from "../common/utils/app-version";
import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
import { isDevelopment, isTestEnv, isWindows } from "../common/vars";
import { helmCli } from "./helm/helm-cli";
import logger from "./logger";
import { promiseExec } from "./promise-exec";
const pipeline = promisify(stream.pipeline);
const bundledVersion = getBundledKubectlVersion();
const kubectlMap: Map<string, string> = new Map([
@ -273,33 +279,22 @@ export class Kubectl {
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
return new Promise((resolve, reject) => {
const stream = customRequest({
url: this.url,
gzip: true,
});
const file = fs.createWriteStream(this.path);
logger.info(`Downloading kubectl ${this.kubectlVersion} from ${this.url} to ${this.path}`);
const options: GotStreamFunctionOptions = {
...defaultGotOptions(),
decompress: true,
isStream: true,
};
stream.on("complete", () => {
logger.debug("kubectl binary download finished");
file.end();
});
stream.on("error", (error) => {
logger.error(error);
fs.unlink(this.path, () => {
// do nothing
});
reject(error);
});
file.on("close", () => {
logger.debug("kubectl binary download closed");
fs.chmod(this.path, 0o755, (err) => {
if (err) reject(err);
});
resolve();
});
stream.pipe(file);
});
try {
// TODO: improve the UI for this to use some sort of loading bar TUI
await pipeline(
got.stream(this.url, options),
fs.createWriteStream(this.path)
);
} catch (err) {
logger.error("Failed to download kubectl:", err);
}
}
protected async writeInitScripts() {

View File

@ -1,17 +1,22 @@
import path from "path";
import fs from "fs";
import request from "request";
import { ensureDir, pathExists } from "fs-extra";
import fse from "fs-extra";
import * as tar from "tar";
import { isWindows } from "../common/vars";
import winston from "winston";
import { GotStreamFunctionOptions } from "../common/got-opts";
import got from "got";
import stream from "stream";
import { promisify } from "util";
const pipeline = promisify(stream.pipeline);
export type LensBinaryOpts = {
version: string;
baseDir: string;
originalBinaryName: string;
newBinaryName?: string;
requestOpts?: request.Options;
requestOpts?: GotStreamFunctionOptions;
};
export class LensBinary {
@ -26,7 +31,7 @@ export class LensBinary {
protected platformName: string;
protected arch: string;
protected originalBinaryName: string;
protected requestOpts: request.Options;
protected requestOpts: GotStreamFunctionOptions;
protected logger: Console | winston.Logger;
constructor(opts: LensBinaryOpts) {
@ -63,7 +68,7 @@ export class LensBinary {
}
}
public setLogger(logger: Console | winston.Logger) {
public setLogger(logger: Console | winston.Logger) {
this.logger = logger;
}
@ -110,88 +115,71 @@ export class LensBinary {
}
protected async checkBinary() {
const exists = await pathExists(this.getBinaryPath());
return exists;
return fse.pathExists(this.getBinaryPath());
}
public async ensureBinary() {
const isValid = await this.checkBinary();
try {
if (await this.checkBinary()) {
return;
}
await this.downloadBinary();
if (this.tarPath) {
await this.untarBinary();
}
if (this.originalBinaryName !== this.binaryName) {
await this.renameBinary();
}
if (!isValid) {
await this.downloadBinary().catch((error) => {
this.logger.error(error);
});
if (this.tarPath) await this.untarBinary();
if (this.originalBinaryName != this.binaryName) await this.renameBinary();
this.logger.info(`${this.originalBinaryName} has been downloaded to ${this.getBinaryPath()}`);
} catch (err) {
this.logger.error(err);
}
}
protected async untarBinary() {
return new Promise<void>(resolve => {
this.logger.debug(`Extracting ${this.originalBinaryName} binary`);
tar.x({
this.logger.debug(`Extracting ${this.originalBinaryName} binary`);
try {
await tar.x({
file: this.tarPath,
cwd: this.dirname
}).then((() => {
resolve();
}));
});
});
} catch (err) {
this.logger.error(`failed to extract ${this.originalBinaryName}: `, err);
}
}
protected async renameBinary() {
return new Promise<void>((resolve, reject) => {
this.logger.debug(`Renaming ${this.originalBinaryName} binary to ${this.binaryName}`);
fs.rename(this.getOriginalBinaryPath(), this.getBinaryPath(), (err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
this.logger.debug(`Renaming ${this.originalBinaryName} binary to ${this.binaryName}`);
return fse.promises.rename(this.getOriginalBinaryPath(), this.getBinaryPath());
}
protected async downloadBinary() {
const binaryPath = this.tarPath || this.getBinaryPath();
await ensureDir(this.getBinaryDir(), 0o755);
await fse.ensureDir(this.getBinaryDir(), 0o755);
const file = fs.createWriteStream(binaryPath);
const url = this.getUrl();
this.logger.info(`Downloading ${this.originalBinaryName} ${this.binaryVersion} from ${url} to ${binaryPath}`);
const requestOpts: request.UriOptions & request.CoreOptions = {
uri: url,
gzip: true,
const requestOpts: GotStreamFunctionOptions = {
decompress: true,
isStream: true,
...this.requestOpts
};
const stream = request(requestOpts);
stream.on("complete", () => {
this.logger.info(`Download of ${this.originalBinaryName} finished`);
file.end();
});
this.logger.info(`Downloading ${this.originalBinaryName} ${this.binaryVersion} from ${url} to ${binaryPath}`);
stream.on("error", (error) => {
this.logger.error(error);
fs.unlink(binaryPath, () => {
// do nothing
});
throw(error);
});
return new Promise((resolve, reject) => {
file.on("close", () => {
this.logger.debug(`${this.originalBinaryName} binary download closed`);
if (!this.tarPath) fs.chmod(binaryPath, 0o755, (err) => {
if (err) reject(err);
});
resolve();
});
stream.pipe(file);
});
try {
await pipeline(
got.stream(url, requestOpts),
fs.createWriteStream(binaryPath),
);
} catch (err) {
this.logger.error("Failed to download kubectl:", err);
}
}
}

View File

@ -1,6 +1,6 @@
import { CoreV1Api } from "@kubernetes/client-node";
export type PrometheusClusterQuery = {
export interface PrometheusClusterQuery {
memoryUsage: string;
memoryRequests: string;
memoryLimits: string;
@ -11,18 +11,18 @@ export type PrometheusClusterQuery = {
cpuCapacity: string;
podUsage: string;
podCapacity: string;
};
}
export type PrometheusNodeQuery = {
export interface PrometheusNodeQuery {
memoryUsage: string;
memoryCapacity: string;
cpuUsage: string;
cpuCapacity: string;
fsSize: string;
fsUsage: string;
};
}
export type PrometheusPodQuery = {
export interface PrometheusPodQuery {
memoryUsage: string;
memoryRequests: string;
memoryLimits: string;
@ -32,25 +32,26 @@ export type PrometheusPodQuery = {
fsUsage: string;
networkReceive: string;
networkTransmit: string;
};
}
export type PrometheusPvcQuery = {
export interface PrometheusPvcQuery {
diskUsage: string;
diskCapacity: string;
};
}
export type PrometheusIngressQuery = {
export interface PrometheusIngressQuery {
bytesSentSuccess: string;
bytesSentFailure: string;
requestDurationSeconds: string;
responseDurationSeconds: string;
};
}
export type PrometheusQueryOpts = {
[key: string]: string | any;
};
export type PrometheusQuery = PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery;
export type PrometheusQueryKey = keyof PrometheusNodeQuery | keyof PrometheusClusterQuery | keyof PrometheusPodQuery | keyof PrometheusPvcQuery | keyof PrometheusIngressQuery;
export type PrometheusService = {
id: string;

View File

@ -2,3 +2,13 @@ import * as util from "util";
import { exec } from "child_process";
export const promiseExec = util.promisify(exec);
export async function stdOptimizedPromiseExec(url: string): Promise<string> {
try {
const { stdout } = await promiseExec(url);
return stdout;
} catch ({ stderr }) {
throw stderr;
}
}

View File

@ -52,7 +52,7 @@ class HelmApiRoute extends LensApi {
const { cluster, params, payload, response } = request;
try {
const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload );
const result = await helmService.updateRelease(cluster, params.release, params.namespace, payload);
this.respondJson(response, result);
} catch (error) {
@ -81,7 +81,7 @@ class HelmApiRoute extends LensApi {
const result = await helmService.listReleases(cluster, params.namespace);
this.respondJson(response, result);
} catch(error) {
} catch (error) {
logger.debug(error);
this.respondText(response, error, 422);
}

View File

@ -4,10 +4,15 @@ import { LensApi } from "../lens-api";
import { Cluster, ClusterMetadataKey } from "../cluster";
import { ClusterPrometheusMetadata } from "../../common/cluster-store";
import logger from "../logger";
import { PrometheusQueryKey } from "../prometheus/provider-registry";
export type IMetricsQuery = string | string[] | {
export interface MetricsQuery {
[metricName: string]: string;
};
}
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// This is used for backoff retry tracking.
const MAX_ATTEMPTS = 5;
@ -29,7 +34,7 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
throw new Error("Metrics not available");
}
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
await delay((attempt + 1) * 1000); // add delay before repeating request
}
}
}
@ -40,9 +45,14 @@ async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPa
return Promise.all(queries.map(loadMetric));
}
export interface MetricsResult {
status?: string;
data?: any;
}
class MetricsRoute extends LensApi {
async routeMetrics({ response, cluster, payload, query }: LensApiRequest) {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const queryParams: MetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try {
@ -72,7 +82,7 @@ class MetricsRoute extends LensApi {
this.respondJson(response, data);
} else {
const queries = Object.entries(payload).map(([queryName, queryOpts]) => (
(prometheusProvider.getQueries(queryOpts) as Record<string, string>)[queryName]
(prometheusProvider.getQueries(queryOpts) as Record<PrometheusQueryKey, string>)[queryName as PrometheusQueryKey]
));
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));

View File

@ -2,7 +2,7 @@
import moment from "moment";
import { apiBase } from "../index";
import type { IMetricsQuery } from "../../../main/routes/metrics-route";
import type { MetricsQuery } from "../../../main/routes/metrics-route";
export interface IMetrics {
status: string;
@ -34,7 +34,7 @@ export interface IMetricsReqParams {
}
export const metricsApi = {
async getMetrics<T = IMetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
async getMetrics<T = MetricsQuery>(query: T, reqParams: IMetricsReqParams = {}): Promise<T extends object ? { [K in keyof T]: IMetrics } : IMetrics> {
const { range = 3600, step = 60, namespace } = reqParams;
let { start, end } = reqParams;

View File

@ -13,7 +13,8 @@ export default function (): webpack.Configuration {
context: __dirname,
target: "electron-main",
mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-eval-source-map",
// devtool: isProduction ? "source-map" : "cheap-eval-source-map",
devtool: "source-map",
cache: isDevelopment,
entry: {
main: path.resolve(mainDir, "index.ts"),

129
yarn.lock
View File

@ -972,6 +972,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
"@sindresorhus/is@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.0.tgz#2ff674e9611b45b528896d820d3d7a812de2f0e4"
integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==
"@sinonjs/commons@^1.7.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
@ -993,10 +998,17 @@
dependencies:
defer-to-connect "^1.0.1"
"@szmarczak/http-timer@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
dependencies:
defer-to-connect "^2.0.0"
"@testing-library/dom@>=7":
version "7.29.4"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c"
integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==
version "7.30.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.0.tgz#53697851f7708a1448cc30b74a2ea056dd709cd6"
integrity sha512-v4GzWtltaiDE0yRikLlcLAfEiiK8+ptu6OuuIebm9GdC2XlZTNDPGEfM2UkEtnH7hr9TRq2sivT5EA9P1Oy7bw==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
@ -1106,6 +1118,16 @@
dependencies:
"@types/node" "*"
"@types/cacheable-request@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
dependencies:
"@types/http-cache-semantics" "*"
"@types/keyv" "*"
"@types/node" "*"
"@types/responselike" "*"
"@types/caseless@*":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
@ -1308,6 +1330,11 @@
"@types/tapable" "*"
"@types/webpack" "*"
"@types/http-cache-semantics@*":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
"@types/http-proxy-middleware@*":
version "0.19.3"
resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03"
@ -1411,6 +1438,13 @@
resolved "https://registry.yarnpkg.com/@types/jsonpath/-/jsonpath-0.2.0.tgz#13c62db22a34d9c411364fac79fd374d63445aa1"
integrity sha512-v7qlPA0VpKUlEdhghbDqRoKMxFB3h3Ch688TApBJ6v+XLDdvWCGLJIYiPKGZnS6MAOie+IorCfNYVHOPIHSWwQ==
"@types/keyv@*":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
dependencies:
"@types/node" "*"
"@types/lodash@^4.14.155":
version "4.14.155"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.155.tgz#e2b4514f46a261fd11542e47519c20ebce7bc23a"
@ -1665,6 +1699,13 @@
"@types/tough-cookie" "*"
form-data "^2.5.0"
"@types/responselike@*", "@types/responselike@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
dependencies:
"@types/node" "*"
"@types/retry@*":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
@ -3262,6 +3303,11 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
cacheable-lookup@^5.0.3:
version "5.0.4"
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
cacheable-request@^2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
@ -3288,6 +3334,19 @@ cacheable-request@^6.0.0:
normalize-url "^4.1.0"
responselike "^1.0.2"
cacheable-request@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58"
integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==
dependencies:
clone-response "^1.0.2"
get-stream "^5.1.0"
http-cache-semantics "^4.0.0"
keyv "^4.0.0"
lowercase-keys "^2.0.0"
normalize-url "^4.1.0"
responselike "^2.0.0"
call-bind@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce"
@ -4436,6 +4495,11 @@ defer-to-connect@^1.0.1:
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
defer-to-connect@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1"
integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -6328,6 +6392,23 @@ globule@^1.0.0:
lodash "~4.17.10"
minimatch "~3.0.2"
got@^11.8.1:
version "11.8.1"
resolved "https://registry.yarnpkg.com/got/-/got-11.8.1.tgz#df04adfaf2e782babb3daabc79139feec2f7e85d"
integrity sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q==
dependencies:
"@sindresorhus/is" "^4.0.0"
"@szmarczak/http-timer" "^4.0.5"
"@types/cacheable-request" "^6.0.1"
"@types/responselike" "^1.0.0"
cacheable-lookup "^5.0.3"
cacheable-request "^7.0.1"
decompress-response "^6.0.0"
http2-wrapper "^1.0.0-beta.5.2"
lowercase-keys "^2.0.0"
p-cancelable "^2.0.0"
responselike "^2.0.0"
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@ -6606,6 +6687,11 @@ hpack.js@^2.1.6:
readable-stream "^2.0.1"
wbuf "^1.1.0"
hpagent@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-0.1.1.tgz#66f67f16e5c7a8b59a068e40c2658c2c749ad5e2"
integrity sha512-IxJWQiY0vmEjetHdoE9HZjD4Cx+mYTr25tR7JCxXaiI3QxW0YqYyM11KyZbHufoa/piWhMb2+D3FGpMgmA2cFQ==
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@ -6751,6 +6837,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
http2-wrapper@^1.0.0-beta.5.2:
version "1.0.0-beta.5.2"
resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3"
integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==
dependencies:
quick-lru "^5.1.1"
resolve-alpn "^1.0.0"
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@ -8114,6 +8208,11 @@ json-buffer@3.0.0:
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -8315,6 +8414,13 @@ keyv@^3.0.0:
dependencies:
json-buffer "3.0.0"
keyv@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
dependencies:
json-buffer "3.0.1"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -11177,6 +11283,11 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
qw@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
@ -11761,6 +11872,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resolve-alpn@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c"
integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@ -11830,6 +11946,13 @@ responselike@1.0.2, responselike@^1.0.2:
dependencies:
lowercase-keys "^1.0.0"
responselike@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
dependencies:
lowercase-keys "^2.0.0"
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"