Merge branch 'master' into app-modal-registry

This commit is contained in:
Alex Andreev committed 2022-10-10 10:17:32 +03:00
commit 3bdbf2bc46
445 files changed
+19436 -6675

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import https from "https";
import os from "os";
import { getMacRootCA, getWinRootCA, injectCAs, DSTRootCAX3 } from "../system-ca";
import { dependencies, devDependencies } from "../../../package.json";
import assert from "assert";
const deps = { ...dependencies, ...devDependencies };
// Skip the test if mac-ca is not installed, or os is not darwin
(deps["mac-ca"] && os.platform().includes("darwin") ? describe: describe.skip)("inject CA for Mac", () => {
// for reset https.globalAgent.options.ca after testing
let _ca: string | Buffer | (string | Buffer)[] | undefined;
beforeEach(() => {
_ca = https.globalAgent.options.ca;
});
afterEach(() => {
https.globalAgent.options.ca = _ca;
});
/**
* The test to ensure using getMacRootCA + injectCAs injects CAs in the same way as using
* the auto injection (require('mac-ca'))
*/
it("should inject the same ca as mac-ca", async () => {
const osxCAs = await getMacRootCA();
injectCAs(osxCAs);
const injected = https.globalAgent.options.ca as (string | Buffer)[];
await import("mac-ca");
const injectedByMacCA = https.globalAgent.options.ca as (string | Buffer)[];
expect(new Set(injected)).toEqual(new Set(injectedByMacCA));
});
it("shouldn't included the expired DST Root CA X3 on Mac", async () => {
const osxCAs = await getMacRootCA();
injectCAs(osxCAs);
const injected = https.globalAgent.options.ca;
assert(injected);
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
});
});
// Skip the test if win-ca is not installed, or os is not win32
(deps["win-ca"] && os.platform().includes("win32") ? describe: describe.skip)("inject CA for Windows", () => {
// for reset https.globalAgent.options.ca after testing
let _ca: string | Buffer | (string | Buffer)[] | undefined;
beforeEach(() => {
_ca = https.globalAgent.options.ca;
});
afterEach(() => {
https.globalAgent.options.ca = _ca;
});
/**
* The test to ensure using win-ca/api injects CAs in the same way as using
* the auto injection (require('win-ca').inject('+'))
*/
it("should inject the same ca as winca.inject('+')", async () => {
const winCAs = await getWinRootCA();
const wincaAPI = await import("win-ca/api");
wincaAPI.inject("+", winCAs);
const injected = https.globalAgent.options.ca as (string | Buffer)[];
const winca = await import("win-ca");
winca.inject("+"); // see: https://github.com/ukoloff/win-ca#caveats
const injectedByWinCA = https.globalAgent.options.ca as (string | Buffer)[];
expect(new Set(injected)).toEqual(new Set(injectedByWinCA));
});
it("shouldn't included the expired DST Root CA X3 on Windows", async () => {
const winCAs = await getWinRootCA();
const wincaAPI = await import("win-ca/api");
wincaAPI.inject("true", winCAs);
const injected = https.globalAgent.options.ca as (string | Buffer)[];
expect(injected.includes(DSTRootCAX3)).toBeFalsy();
});
});
@@ -4,16 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
import joinPathsInjectable from "../../path/join-paths.injectable";
const directoryForBinariesInjectable = getInjectable({
id: "directory-for-binaries",
instantiate: (di) => {
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const directoryForUserData = di.inject(directoryForUserDataInjectable);
return getAbsolutePath(directoryForUserData, "binaries");
return joinPaths(directoryForUserData, "binaries");
},
});
@@ -4,19 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
import joinPathsInjectable from "../../path/join-paths.injectable";
const directoryForKubeConfigsInjectable = getInjectable({
id: "directory-for-kube-configs",
instantiate: (di) => {
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const directoryForUserData = di.inject(directoryForUserDataInjectable);
return getAbsolutePath(
directoryForUserData,
"kubeconfigs",
);
return joinPaths(directoryForUserData, "kubeconfigs");
},
});
@@ -4,17 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForBinariesInjectable from "../directory-for-binaries/directory-for-binaries.injectable";
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
import joinPathsInjectable from "../../path/join-paths.injectable";
const directoryForKubectlBinariesInjectable = getInjectable({
id: "directory-for-kubectl-binaries",
instantiate: (di) => {
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const directoryForBinaries = di.inject(directoryForBinariesInjectable);
return getAbsolutePath(directoryForBinaries, "kubectl");
return joinPaths(directoryForBinaries, "kubectl");
},
});
@@ -4,17 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
import joinPathsInjectable from "../../path/join-paths.injectable";
const getCustomKubeConfigDirectoryInjectable = getInjectable({
id: "get-custom-kube-config-directory",
instantiate: (di) => {
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const joinPaths = di.inject(joinPathsInjectable);
return (directoryName: string) =>
getAbsolutePath(directoryForKubeConfigs, directoryName);
return (directoryName: string) => joinPaths(directoryForKubeConfigs, directoryName);
},
});
@@ -0,0 +1,18 @@
/**
* 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 { computed } from "mobx";
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
const filteredCategoriesInjectable = getInjectable({
id: "filtered-categories",
instantiate: (di) => {
const registry = di.inject(catalogCategoryRegistryInjectable);
return computed(() => [...registry.filteredItems]);
},
});
export default filteredCategoriesInjectable;
@@ -1,39 +0,0 @@
/**
* 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 { globalAgent } from "https";
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
// DST Root CA X3, which was expired on 9.30.2021
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";
function isCertActive(cert: string) {
const isExpired = typeof cert !== "string" || cert.includes(DSTRootCAX3);
return !isExpired;
}
const injectSystemCAsInjectable = getInjectable({
id: "inject-system-cas",
instantiate: (di) => {
const requestSystemCAs = di.inject(requestSystemCAsInjectionToken);
return async () => {
for (const cert of await requestSystemCAs()) {
if (isCertActive(cert)) {
if (Array.isArray(globalAgent.options.ca) && !globalAgent.options.ca.includes(cert)) {
globalAgent.options.ca.push(cert);
} else {
globalAgent.options.ca = [cert];
}
}
}
};
},
});
export default injectSystemCAsInjectable;
@@ -1,10 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
export const requestSystemCAsInjectionToken = getInjectionToken<() => Promise<string[]>>({
id: "request-system-cas-token",
});
@@ -1,44 +0,0 @@
/**
* 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 execFileInjectable from "../fs/exec-file.injectable";
import loggerInjectable from "../logger.injectable";
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet#other_assertions
const certSplitPattern = /(?=-----BEGIN\sCERTIFICATE-----)/g;
const requestSystemCAsInjectable = getInjectable({
id: "request-system-cas",
instantiate: (di) => {
const execFile = di.inject(execFileInjectable);
const logger = di.inject(loggerInjectable);
const execSecurity = async (...args: string[]) => {
const output = await execFile("/usr/bin/security", args);
return output.split(certSplitPattern);
};
return async () => {
try {
const [trusted, rootCA] = await Promise.all([
execSecurity("find-certificate", "-a", "-p"),
execSecurity("find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain"),
]);
return [...new Set([...trusted, ...rootCA])];
} catch (error) {
logger.warn(`[INJECT-CAS]: Error injecting root CAs from MacOSX: ${error}`);
}
return [];
};
},
causesSideEffects: true,
injectionToken: requestSystemCAsInjectionToken,
});
export default requestSystemCAsInjectable;
@@ -1,14 +0,0 @@
/**
* 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 { requestSystemCAsInjectionToken } from "./request-system-cas-token";
const requestSystemCAsInjectable = getInjectable({
id: "request-system-cas",
instantiate: () => async () => [],
injectionToken: requestSystemCAsInjectionToken,
});
export default requestSystemCAsInjectable;
@@ -1,14 +0,0 @@
/**
* 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 { requestSystemCAsInjectionToken } from "./request-system-cas-token";
const requestSystemCAsInjectable = getInjectable({
id: "request-system-cas",
instantiate: () => async () => [],
injectionToken: requestSystemCAsInjectionToken,
});
export default requestSystemCAsInjectable;
@@ -1,45 +0,0 @@
/**
* 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 execFileInjectable from "../fs/exec-file.injectable";
import { requestSystemCAsInjectionToken } from "./request-system-cas-token";
const pemEncoding = (hexEncodedCert: String) => {
const certData = Buffer.from(hexEncodedCert, "hex").toString("base64");
const lines = ["-----BEGIN CERTIFICATE-----"];
for (let i = 0; i < certData.length; i += 64) {
lines.push(certData.substring(i, i + 64));
}
lines.push("-----END CERTIFICATE-----", "");
return lines.join("\r\n");
};
const requestSystemCAsInjectable = getInjectable({
id: "request-system-cas",
instantiate: (di) => {
const wincaRootsExePath: string = __non_webpack_require__.resolve("win-ca/lib/roots.exe");
const execFile = di.inject(execFileInjectable);
return async () => {
/**
* This needs to be done manually because for some reason calling the api from "win-ca"
* directly fails to load "child_process" correctly on renderer
*/
const output = await execFile(wincaRootsExePath);
return output
.split("\r\n")
.filter(Boolean)
.map(pemEncoding);
};
},
causesSideEffects: true,
injectionToken: requestSystemCAsInjectionToken,
});
export default requestSystemCAsInjectable;
+4 -3
View File
@@ -9,7 +9,6 @@ import type { KubeConfig } from "@kubernetes/client-node";
import { HttpError } from "@kubernetes/client-node";
import type { Kubectl } from "../../main/kubectl/kubectl";
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
import { loadConfigFromFile } from "../kube-helpers";
import type { KubeApiResource, KubeResource } from "../rbac";
import { apiResourceRecord, apiResources } from "../rbac";
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
@@ -25,6 +24,7 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
import assert from "assert";
import type { Logger } from "../logger";
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
export interface ClusterDependencies {
readonly directoryForKubeConfigs: string;
@@ -37,6 +37,7 @@ export interface ClusterDependencies {
createListNamespaces: (config: KubeConfig) => ListNamespaces;
createVersionDetector: (cluster: Cluster) => VersionDetector;
broadcastMessage: BroadcastMessage;
loadConfigfromFile: LoadConfigfromFile;
}
/**
@@ -500,7 +501,7 @@ export class Cluster implements ClusterModel, ClusterState {
}
async getKubeconfig(): Promise<KubeConfig> {
const { config } = await loadConfigFromFile(this.kubeConfigPath);
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
return config;
}
@@ -510,7 +511,7 @@ export class Cluster implements ClusterModel, ClusterState {
*/
async getProxyKubeconfig(): Promise<KubeConfig> {
const proxyKCPath = await this.getProxyKubeconfigPath();
const { config } = await loadConfigFromFile(proxyKCPath);
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
return config;
}
@@ -4,16 +4,16 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
import joinPathsInjectable from "../path/join-paths.injectable";
const directoryForLensLocalStorageInjectable = getInjectable({
id: "directory-for-lens-local-storage",
instantiate: (di) => {
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
const joinPaths = di.inject(joinPathsInjectable);
const directoryForUserData = di.inject(directoryForUserDataInjectable);
return getAbsolutePath(
return joinPaths(
directoryForUserData,
"lens-local-storage",
);
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import fetchInjectable from "./fetch.injectable";
export default getGlobalOverride(fetchInjectable, () => () => {
throw new Error("tried to fetch a resource without override in test");
});
+17
View File
@@ -0,0 +1,17 @@
/**
* 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 type { RequestInfo, RequestInit, Response } from "node-fetch";
import fetch from "node-fetch";
export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
const fetchInjectable = getInjectable({
id: "fetch",
instantiate: (): Fetch => fetch,
causesSideEffects: true,
});
export default fetchInjectable;
@@ -0,0 +1,20 @@
/**
* 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 runtimeClassesRouteInjectable from "./runtime-classes-route.injectable";
import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token";
const navigateToRuntimeClassesInjectable = getInjectable({
id: "navigate-to-runtime-classes",
instantiate: (di) => {
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
const route = di.inject(runtimeClassesRouteInjectable);
return () => navigateToRoute(route);
},
});
export default navigateToRuntimeClassesInjectable;
@@ -0,0 +1,25 @@
/**
* 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 isAllowedResourceInjectable from "../../../../../utils/is-allowed-resource.injectable";
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
const runtimeClassesRouteInjectable = getInjectable({
id: "runtime-classes-route",
instantiate: (di) => {
const isAllowedResource = di.inject(isAllowedResourceInjectable, "runtimeclasses");
return {
path: "/runtimeclasses",
clusterFrame: true,
isEnabled: isAllowedResource,
};
},
injectionToken: frontEndRouteInjectionToken,
});
export default runtimeClassesRouteInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import accessPathInjectable from "./access-path.injectable";
export default getGlobalOverride(accessPathInjectable, () => async () => {
throw new Error("tried to verify path access without override");
});
+27
View File
@@ -0,0 +1,27 @@
/**
* 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 fsInjectable from "./fs.injectable";
export type AccessPath = (path: string, mode?: number) => Promise<boolean>;
const accessPathInjectable = getInjectable({
id: "access-path",
instantiate: (di): AccessPath => {
const { access } = di.inject(fsInjectable);
return async (path, mode) => {
try {
await access(path, mode);
return true;
} catch {
return false;
}
};
},
});
export default accessPathInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import copyInjectable from "./copy.injectable";
export default getGlobalOverride(copyInjectable, () => async () => {
throw new Error("tried to copy filepaths without override");
});
+16
View File
@@ -0,0 +1,16 @@
/**
* 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 type { CopyOptions } from "fs-extra";
import fsInjectable from "./fs.injectable";
export type Copy = (src: string, dest: string, options?: CopyOptions | undefined) => Promise<void>;
const copyInjectable = getInjectable({
id: "copy",
instantiate: (di): Copy => di.inject(fsInjectable).copy,
});
export default copyInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import deleteFileInjectable from "./delete-file.injectable";
export default getGlobalOverride(deleteFileInjectable, () => async () => {
throw new Error("tried to delete file without override");
});
+15
View File
@@ -0,0 +1,15 @@
/**
* 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 fsInjectable from "./fs.injectable";
export type DeleteFile = (filePath: string) => Promise<void>;
const deleteFileInjectable = getInjectable({
id: "delete-file",
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
});
export default deleteFileInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import ensureDirInjectable from "./ensure-dir.injectable";
export default getGlobalOverride(ensureDirInjectable, () => async () => {
throw new Error("tried to ensure directory without override");
});
+3 -3
View File
@@ -5,14 +5,14 @@
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type EnsureDirectory = (dirPath: string) => Promise<void>;
const ensureDirInjectable = getInjectable({
id: "ensure-dir",
// TODO: Remove usages of ensureDir from business logic.
// TODO: Read, Write, Watch etc. operations should do this internally.
instantiate: (di) => di.inject(fsInjectable).ensureDir,
causesSideEffects: true,
instantiate: (di): EnsureDirectory => di.inject(fsInjectable).ensureDir,
});
export default ensureDirInjectable;
+3 -21
View File
@@ -7,11 +7,7 @@ import type { ExecFileOptions } from "child_process";
import { execFile } from "child_process";
import { promisify } from "util";
export interface ExecFile {
(filePath: string): Promise<string>;
(filePath: string, argsOrOptions: string[] | ExecFileOptions): Promise<string>;
(filePath: string, args: string[], options: ExecFileOptions): Promise<string>;
}
export type ExecFile = (filePath: string, args: string[], options: ExecFileOptions) => Promise<string>;
const execFileInjectable = getInjectable({
id: "exec-file",
@@ -19,22 +15,8 @@ const execFileInjectable = getInjectable({
instantiate: (): ExecFile => {
const asyncExecFile = promisify(execFile);
return async (filePath: string, argsOrOptions?: string[] | ExecFileOptions, maybeOptions?: ExecFileOptions) => {
let args: string[];
let options: ExecFileOptions;
if (Array.isArray(argsOrOptions)) {
args = argsOrOptions;
options = maybeOptions ?? {};
} else {
args = [];
options = maybeOptions ?? argsOrOptions ?? {};
}
const result = await asyncExecFile(filePath, args, {
encoding: "utf-8",
...options,
});
return async (filePath, args, options) => {
const result = await asyncExecFile(filePath, args, options);
return result.stdout;
};
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import extractTarInjectable from "./extract-tar.injectable";
export default getGlobalOverride(extractTarInjectable, () => async () => {
throw new Error("tried to extract a tar file without override");
});
+26
View File
@@ -0,0 +1,26 @@
/**
* 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 type { ExtractOptions } from "tar";
import { extract } from "tar";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
export type ExtractTar = (filePath: string, opts?: ExtractOptions) => Promise<void>;
const extractTarInjectable = getInjectable({
id: "extract-tar",
instantiate: (di): ExtractTar => {
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return (filePath, opts = {}) => extract({
file: filePath,
cwd: getDirnameOfPath(filePath),
...opts,
});
},
causesSideEffects: true,
});
export default extractTarInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import lstatInjectable from "./lstat.injectable";
export default getGlobalOverride(lstatInjectable, () => async () => {
throw new Error("tried to lstat a filepath without override");
});
+16
View File
@@ -0,0 +1,16 @@
/**
* 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 type { Stats } from "fs";
import fsInjectable from "./fs.injectable";
export type LStat = (path: string) => Promise<Stats>;
const lstatInjectable = getInjectable({
id: "lstat",
instantiate: (di): LStat => di.inject(fsInjectable).lstat,
});
export default lstatInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import moveInjectable from "./move.injectable";
export default getGlobalOverride(moveInjectable, () => async () => {
throw new Error("tried to move without override");
});
+16
View File
@@ -0,0 +1,16 @@
/**
* 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 type { MoveOptions } from "fs-extra";
import fsInjectable from "./fs.injectable";
export type Move = (src: string, dest: string, options?: MoveOptions) => Promise<void>;
const moveInjectable = getInjectable({
id: "move",
instantiate: (di): Move => di.inject(fsInjectable).move,
});
export default moveInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import pathExistsInjectable from "./path-exists.injectable";
export default getGlobalOverride(pathExistsInjectable, () => async () => {
throw new Error("Tried to check if a path exists without override");
});
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import readDirectoryInjectable from "./read-directory.injectable";
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
throw new Error("tried to read a directory's content without override");
});
@@ -0,0 +1,37 @@
/**
* 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 type { Dirent } from "fs";
import fsInjectable from "./fs.injectable";
export interface ReadDirectory {
(
path: string,
options: "buffer" | { encoding: "buffer"; withFileTypes?: false | undefined }
): Promise<Buffer[]>;
(
path: string,
options?:
| { encoding: BufferEncoding | string | null; withFileTypes?: false | undefined }
| BufferEncoding
| string
| null,
): Promise<string[]>;
(
path: string,
options?: { encoding?: BufferEncoding | string | null | undefined; withFileTypes?: false | undefined },
): Promise<string[] | Buffer[]>;
(
path: string,
options: { encoding?: BufferEncoding | string | null | undefined; withFileTypes: true },
): Promise<Dirent[]>;
}
const readDirectoryInjectable = getInjectable({
id: "read-directory",
instantiate: (di): ReadDirectory => di.inject(fsInjectable).readdir,
});
export default readDirectoryInjectable;
+7 -2
View File
@@ -5,11 +5,16 @@
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type ReadFile = (filePath: string) => Promise<string>;
const readFileInjectable = getInjectable({
id: "read-file",
instantiate: (di) => (filePath: string) =>
di.inject(fsInjectable).readFile(filePath, "utf-8"),
instantiate: (di): ReadFile => {
const { readFile } = di.inject(fsInjectable);
return (filePath) => readFile(filePath, "utf-8");
},
});
export default readFileInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import removePathInjectable from "./remove-path.injectable";
export default getGlobalOverride(removePathInjectable, () => async () => {
throw new Error("tried to remove a path without override");
});
@@ -5,9 +5,11 @@
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
const readDirInjectable = getInjectable({
id: "read-dir",
instantiate: (di) => di.inject(fsInjectable).readdir,
export type RemovePath = (path: string) => Promise<void>;
const removePathInjectable = getInjectable({
id: "remove-path",
instantiate: (di): RemovePath => di.inject(fsInjectable).remove,
});
export default readDirInjectable;
export default removePathInjectable;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import writeFileInjectable from "./write-file.injectable";
export default getGlobalOverride(writeFileInjectable, () => async () => {
throw new Error("tried to write file without override");
});
+12 -4
View File
@@ -3,20 +3,28 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import path from "path";
import type { WriteFileOptions } from "fs-extra";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
import fsInjectable from "./fs.injectable";
export type WriteFile = (filePath: string, content: string | Buffer, opts?: WriteFileOptions) => Promise<void>;
const writeFileInjectable = getInjectable({
id: "write-file",
instantiate: (di) => {
instantiate: (di): WriteFile => {
const { writeFile, ensureDir } = di.inject(fsInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return async (filePath: string, content: string | Buffer) => {
await ensureDir(path.dirname(filePath), { mode: 0o755 });
return async (filePath, content, opts) => {
await ensureDir(getDirnameOfPath(filePath), {
mode: 0o755,
...(opts ?? {}),
});
await writeFile(filePath, content, {
encoding: "utf-8",
...(opts ?? {}),
});
};
},
+11 -21
View File
@@ -3,37 +3,27 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { EnsureOptions, WriteOptions } from "fs-extra";
import path from "path";
import type { JsonValue } from "type-fest";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
import fsInjectable from "./fs.injectable";
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
interface Dependencies {
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
}
const writeJsonFile = ({ writeJson, ensureDir }: Dependencies): WriteJson => async (filePath, content) => {
await ensureDir(path.dirname(filePath), { mode: 0o755 });
await writeJson(filePath, content, {
encoding: "utf-8",
spaces: 2,
});
};
const writeJsonFileInjectable = getInjectable({
id: "write-json-file",
instantiate: (di) => {
instantiate: (di): WriteJson => {
const { writeJson, ensureDir } = di.inject(fsInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return writeJsonFile({
writeJson,
ensureDir,
});
return async (filePath, content) => {
await ensureDir(getDirnameOfPath(filePath), { mode: 0o755 });
await writeJson(filePath, content, {
encoding: "utf-8",
spaces: 2,
});
};
},
});
-3
View File
@@ -8,9 +8,6 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
export const clusterVisibilityHandler = "cluster:visibility";
export const clusterRefreshHandler = "cluster:refresh";
export const clusterDisconnectHandler = "cluster:disconnect";
export const clusterDeleteHandler = "cluster:delete";
export const clusterSetDeletingHandler = "cluster:deleting:set";
export const clusterClearDeletingHandler = "cluster:deleting:clear";
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
export const clusterStates = "cluster:states";
File diff suppressed because it is too large. Load diff
+5 -34
View File
@@ -3,38 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { JsonApi } from "./json-api";
import { apiPrefix, isDebugging, isDevelopment } from "../vars";
import { appEventBus } from "../app-event-bus/event-bus";
import type { JsonApi } from "./json-api";
import { getInjectionToken } from "@ogre-tools/injectable";
export let apiBase: JsonApi;
if (typeof window === "undefined") {
appEventBus.addListener((event) => {
if (event.name !== "lens-proxy" && event.action !== "listen") return;
const params = event.params as { port?: number };
if (!params.port) return;
apiBase = new JsonApi({
serverAddress: `http://127.0.0.1:${params.port}`,
apiBase: apiPrefix,
debug: isDevelopment || isDebugging,
}, {
headers: {
"Host": `localhost:${params.port}`,
},
});
});
} else {
apiBase = new JsonApi({
serverAddress: `http://127.0.0.1:${window.location.port}`,
apiBase: apiPrefix,
debug: isDevelopment || isDebugging,
}, {
headers: {
"Host": window.location.host,
},
});
}
export const apiBaseInjectionToken = getInjectionToken<JsonApi>({
id: "api-base-token",
});
-3
View File
@@ -4,11 +4,8 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import type { KubeJsonApi } from "./kube-json-api";
export const apiKubeInjectionToken = getInjectionToken<KubeJsonApi>({
id: "api-kube-injection-token",
});
export const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken);
@@ -0,0 +1,26 @@
/**
* 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 type { RequestInit } from "node-fetch";
import fetchInjectable from "../fetch/fetch.injectable";
import loggerInjectable from "../logger.injectable";
import type { JsonApiConfig, JsonApiData, JsonApiDependencies, JsonApiParams } from "./json-api";
import { JsonApi } from "./json-api";
export type CreateJsonApi = <Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>>(config: JsonApiConfig, reqInit?: RequestInit) => JsonApi<Data, Params>;
const createJsonApiInjectable = getInjectable({
id: "create-json-api",
instantiate: (di): CreateJsonApi => {
const deps: JsonApiDependencies = {
fetch: di.inject(fetchInjectable),
logger: di.inject(loggerInjectable),
};
return (config, reqInit) => new JsonApi(deps, config, reqInit);
},
});
export default createJsonApiInjectable;
@@ -0,0 +1,63 @@
/**
* 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 { apiKubePrefix } from "../vars";
import isDevelopmentInjectable from "../vars/is-development.injectable";
import { apiBaseInjectionToken } from "./api-base";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import type { KubeApiOptions } from "./kube-api";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
export interface CreateKubeApiForLocalClusterConfig {
metadata: {
uid: string;
};
}
export interface CreateKubeApiForCluster {
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
cluster: CreateKubeApiForLocalClusterConfig,
kubeClass: KubeObjectConstructor<Object, Data>,
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api
): Api;
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
cluster: CreateKubeApiForLocalClusterConfig,
kubeClass: KubeObjectConstructor<Object, Data>,
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>
): KubeApi<Object>;
}
const createKubeApiForClusterInjectable = getInjectable({
id: "create-kube-api-for-cluster",
instantiate: (di): CreateKubeApiForCluster => {
const apiBase = di.inject(apiBaseInjectionToken);
const isDevelopment = di.inject(isDevelopmentInjectable);
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
return (
cluster: CreateKubeApiForLocalClusterConfig,
kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>,
apiClass = KubeApi,
) => (
new apiClass({
objectConstructor: kubeClass,
request: createKubeJsonApi(
{
serverAddress: apiBase.config.serverAddress,
apiBase: apiKubePrefix,
debug: isDevelopment,
}, {
headers: {
"Host": `${cluster.metadata.uid}.localhost:${new URL(apiBase.config.serverAddress).port}`,
},
},
),
})
);
},
});
export default createKubeApiForClusterInjectable;
@@ -0,0 +1,104 @@
/**
* 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 type { AgentOptions } from "https";
import { Agent } from "https";
import type { RequestInit } from "node-fetch";
import isDevelopmentInjectable from "../vars/is-development.injectable";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import type { KubeApiOptions } from "./kube-api";
import { KubeApi } from "./kube-api";
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
export interface CreateKubeApiForRemoteClusterConfig {
cluster: {
server: string;
caData?: string;
skipTLSVerify?: boolean;
};
user: {
token?: string | (() => Promise<string>);
clientCertificateData?: string;
clientKeyData?: string;
};
/**
* Custom instance of https.agent to use for the requests
*
* @remarks the custom agent replaced default agent, options skipTLSVerify,
* clientCertificateData, clientKeyData and caData are ignored.
*/
agent?: Agent;
}
export interface CreateKubeApiForRemoteCluster {
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
config: CreateKubeApiForRemoteClusterConfig,
kubeClass: KubeObjectConstructor<Object, Data>,
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api,
): Api;
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
config: CreateKubeApiForRemoteClusterConfig,
kubeClass: KubeObjectConstructor<Object, Data>,
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>,
): KubeApi<Object>;
}
const createKubeApiForRemoteClusterInjectable = getInjectable({
id: "create-kube-api-for-remote-cluster",
instantiate: (di): CreateKubeApiForRemoteCluster => {
const isDevelopment = di.inject(isDevelopmentInjectable);
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
return (config: CreateKubeApiForRemoteClusterConfig, kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>, apiClass = KubeApi) => {
const reqInit: RequestInit = {};
const agentOptions: AgentOptions = {};
if (config.cluster.skipTLSVerify === true) {
agentOptions.rejectUnauthorized = false;
}
if (config.user.clientCertificateData) {
agentOptions.cert = config.user.clientCertificateData;
}
if (config.user.clientKeyData) {
agentOptions.key = config.user.clientKeyData;
}
if (config.cluster.caData) {
agentOptions.ca = config.cluster.caData;
}
if (Object.keys(agentOptions).length > 0) {
reqInit.agent = new Agent(agentOptions);
}
if (config.agent) {
reqInit.agent = config.agent;
}
const token = config.user.token;
const request = createKubeJsonApi({
serverAddress: config.cluster.server,
apiBase: "",
debug: isDevelopment,
...(token ? {
getRequestOptions: async () => ({
headers: {
"Authorization": `Bearer ${typeof token === "function" ? await token() : token}`,
},
}),
} : {}),
}, reqInit);
return new apiClass({
objectConstructor: kubeClass,
request,
});
};
},
});
export default createKubeApiForRemoteClusterInjectable;
@@ -0,0 +1,36 @@
/**
* 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 { apiKubePrefix } from "../vars";
import isDebuggingInjectable from "../vars/is-debugging.injectable";
import { apiBaseInjectionToken } from "./api-base";
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
import type { KubeJsonApi } from "./kube-json-api";
export type CreateKubeJsonApiForCluster = (clusterId: string) => KubeJsonApi;
const createKubeJsonApiForClusterInjectable = getInjectable({
id: "create-kube-json-api-for-cluster",
instantiate: (di): CreateKubeJsonApiForCluster => {
const apiBase = di.inject(apiBaseInjectionToken);
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
const isDebugging = di.inject(isDebuggingInjectable);
return (clusterId) => createKubeJsonApi(
{
serverAddress: apiBase.config.serverAddress,
apiBase: apiKubePrefix,
debug: isDebugging,
},
{
headers: {
"Host": `${clusterId}.localhost:${new URL(apiBase.config.serverAddress).port}`,
},
},
);
},
});
export default createKubeJsonApiForClusterInjectable;
@@ -0,0 +1,26 @@
/**
* 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 type { RequestInit } from "node-fetch";
import fetchInjectable from "../fetch/fetch.injectable";
import loggerInjectable from "../logger.injectable";
import type { JsonApiConfig, JsonApiDependencies } from "./json-api";
import { KubeJsonApi } from "./kube-json-api";
export type CreateKubeJsonApi = (config: JsonApiConfig, reqInit?: RequestInit) => KubeJsonApi;
const createKubeJsonApiInjectable = getInjectable({
id: "create-kube-json-api",
instantiate: (di): CreateKubeJsonApi => {
const dependencies: JsonApiDependencies = {
fetch: di.inject(fetchInjectable),
logger: di.inject(loggerInjectable),
};
return (config, reqInit) => new KubeJsonApi(dependencies, config, reqInit);
},
});
export default createKubeJsonApiInjectable;
@@ -3,8 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MetricData, IMetricsReqParams } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
@@ -28,30 +26,6 @@ export class ClusterApi extends KubeApi<Cluster> {
}
}
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<ClusterMetricData> {
const nodes = nodeNames.join("|");
const opts = { category: "cluster", nodes };
return metricsApi.getMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
cpuCapacity: opts,
cpuAllocatableCapacity: opts,
podUsage: opts,
podCapacity: opts,
podAllocatableCapacity: opts,
fsSize: opts,
fsUsage: opts,
}, params);
}
export enum ClusterStatus {
ACTIVE = "Active",
CREATING = "Creating",
@@ -59,21 +33,6 @@ export enum ClusterStatus {
ERROR = "Error",
}
export interface ClusterMetricData extends Partial<Record<string, MetricData>> {
memoryUsage: MetricData;
memoryRequests: MetricData;
memoryLimits: MetricData;
memoryCapacity: MetricData;
cpuUsage: MetricData;
cpuRequests: MetricData;
cpuLimits: MetricData;
cpuCapacity: MetricData;
podUsage: MetricData;
podCapacity: MetricData;
fsSize: MetricData;
fsUsage: MetricData;
}
export interface Cluster {
spec: {
clusterNetwork?: {
@@ -5,8 +5,6 @@
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
@@ -90,20 +88,3 @@ export class DaemonSetApi extends KubeApi<DaemonSet> {
});
}
}
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
+1 -19
View File
@@ -7,8 +7,7 @@ import moment from "moment";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData, PodSpec } from "./pod.api";
import type { PodSpec } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isNumber, isObject } from "../../utils";
@@ -70,23 +69,6 @@ export class DeploymentApi extends KubeApi<Deployment> {
}
}
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface DeploymentSpec {
replicas: number;
selector: LabelSelector;
@@ -3,72 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { compile } from "path-to-regexp";
import { apiBase } from "../index";
import { stringify } from "querystring";
import type { RequestInit } from "node-fetch";
import { autoBind, bifurcateArray, isDefined } from "../../utils";
import { autoBind, bifurcateArray } from "../../utils";
import Joi from "joi";
export type RepoHelmChartList = Record<string, RawHelmChart[]>;
export interface IHelmChartDetails {
readme: string;
versions: HelmChart[];
}
const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
repo?: string;
name?: string;
}) => string;
/**
* Get a list of all helm charts from all saved helm repos
*/
export async function listCharts(): Promise<HelmChart[]> {
const data = await apiBase.get<Record<string, RepoHelmChartList>>(endpoint());
return Object
.values(data)
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array<RawHelmChart[]>())
.map(([chart]) => HelmChart.create(chart, { onError: "log" }))
.filter(isDefined);
}
export interface GetChartDetailsOptions {
version?: string;
reqInit?: RequestInit;
}
/**
* Get the readme and all versions of a chart
* @param repo The repo to get from
* @param name The name of the chart to request the data of
* @param options.version The version of the chart's readme to get, default latest
* @param options.reqInit A way for passing in an abort controller or other browser request options
*/
export async function getChartDetails(repo: string, name: string, { version, reqInit }: GetChartDetailsOptions = {}): Promise<IHelmChartDetails> {
const path = endpoint({ repo, name });
const { readme, ...data } = await apiBase.get<IHelmChartDetails>(`${path}?${stringify({ version })}`, undefined, reqInit);
const versions = data.versions.map(version => HelmChart.create(version, { onError: "log" })).filter(isDefined);
return {
readme,
versions,
};
}
/**
* Get chart values related to a specific repos' version of a chart
* @param repo The repo to get from
* @param name The name of the chart to request the data of
* @param version The version to get the values from
*/
export async function getChartValues(repo: string, name: string, version: string): Promise<string> {
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
}
export interface RawHelmChart {
apiVersion: string;
name: string;
@@ -0,0 +1,34 @@
/**
* 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 { apiBaseInjectionToken } from "../../api-base";
import type { RawHelmChart } from "../helm-charts.api";
import { HelmChart } from "../helm-charts.api";
import { isDefined } from "../../../utils";
export type RequestHelmCharts = () => Promise<HelmChart[]>;
export type RepoHelmChartList = Record<string, RawHelmChart[]>;
/**
* Get a list of all helm charts from all saved helm repos
*/
const requestHelmChartsInjectable = getInjectable({
id: "request-helm-charts",
instantiate: (di) => {
const apiBase = di.inject(apiBaseInjectionToken);
return async () => {
const data = await apiBase.get<Record<string, RepoHelmChartList>>("/v2/charts");
return Object
.values(data)
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array<RawHelmChart[]>())
.map(([chart]) => HelmChart.create(chart, { onError: "log" }))
.filter(isDefined);
};
},
});
export default requestHelmChartsInjectable;
@@ -0,0 +1,24 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
const requestReadmeEndpoint = urlBuilderFor("/v2/charts/:repo/:name/readme");
export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => Promise<string>;
const requestHelmChartReadmeInjectable = getInjectable({
id: "request-helm-chart-readme",
instantiate: (di): RequestHelmChartReadme => {
const apiBase = di.inject(apiBaseInjectionToken);
return (repo, name, version) => (
apiBase.get(requestReadmeEndpoint.compile({ name, repo }, { version }))
);
},
});
export default requestHelmChartReadmeInjectable;
@@ -0,0 +1,24 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
const requestValuesEndpoint = urlBuilderFor("/v2/charts/:repo/:name/values");
export type RequestHelmChartValues = (repo: string, name: string, version: string) => Promise<string>;
const requestHelmChartValuesInjectable = getInjectable({
id: "request-helm-chart-values",
instantiate: (di): RequestHelmChartValues => {
const apiBase = di.inject(apiBaseInjectionToken);
return (repo, name, version) => (
apiBase.get(requestValuesEndpoint.compile({ repo, name }, { version }))
);
},
});
export default requestHelmChartValuesInjectable;
@@ -0,0 +1,31 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
import { HelmChart } from "../helm-charts.api";
import type { RawHelmChart } from "../helm-charts.api";
import { isDefined } from "../../../utils";
const requestVersionsEndpoint = urlBuilderFor("/v2/charts/:repo/:name/versions");
export type RequestHelmChartVersions = (repo: string, chartName: string) => Promise<HelmChart[]>;
const requestHelmChartVersionsInjectable = getInjectable({
id: "request-helm-chart-versions",
instantiate: (di): RequestHelmChartVersions => {
const apiBase = di.inject(apiBaseInjectionToken);
return async (repo, name) => {
const rawVersions = await apiBase.get(requestVersionsEndpoint.compile({ name, repo })) as RawHelmChart[];
return rawVersions
.map(version => HelmChart.create(version, { onError: "log" }))
.filter(isDefined);
};
},
});
export default requestHelmChartVersionsInjectable;
@@ -3,65 +3,14 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { apiBase } from "../index";
import type { ItemObject } from "../../item.store";
import type { JsonApiData } from "../json-api";
import { buildURLPositional } from "../../utils/buildUrl";
import type { HelmReleaseDetails } from "../../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release-details/call-for-helm-release-details.injectable";
import type { HelmReleaseDetails } from "./helm-releases.api/request-details.injectable";
export interface HelmReleaseUpdateDetails {
log: string;
release: HelmReleaseDetails;
}
export interface HelmReleaseRevision {
revision: number;
updated: string;
status: string;
chart: string;
app_version: string;
description: string;
}
type EndpointParams = {}
| { namespace: string }
| { namespace: string; name: string }
| { namespace: string; name: string; route: string };
interface EndpointQuery {
all?: boolean;
}
export const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
const path = endpoint({ name, namespace });
return apiBase.del(path);
}
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
const route = "values";
const path = endpoint({ name, namespace, route }, { all });
return apiBase.get<string>(path);
}
export async function getReleaseHistory(name: string, namespace: string): Promise<HelmReleaseRevision[]> {
const route = "history";
const path = endpoint({ name, namespace, route });
return apiBase.get(path);
}
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
const route = "rollback";
const path = endpoint({ name, namespace, route });
const data = { revision };
return apiBase.put(path, { data });
}
export interface HelmReleaseDto {
appVersion: string;
name: string;
@@ -0,0 +1,11 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../../../test-utils/get-global-override";
import requestHelmReleaseConfigurationInjectable from "./request-configuration.injectable";
export default getGlobalOverride(requestHelmReleaseConfigurationInjectable, () => () => {
throw new Error("Tried to call requestHelmReleaseConfiguration with no override");
});
@@ -0,0 +1,29 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
export type RequestHelmReleaseConfiguration = (
name: string,
namespace: string,
all: boolean
) => Promise<string>;
const requestConfigurationEnpoint = urlBuilderFor("/v2/releases/:namespace/:name/values");
const requestHelmReleaseConfigurationInjectable = getInjectable({
id: "request-helm-release-configuration",
instantiate: (di): RequestHelmReleaseConfiguration => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, namespace, all: boolean) => (
apiBase.get(requestConfigurationEnpoint.compile({ name, namespace }, { all }))
);
},
});
export default requestHelmReleaseConfigurationInjectable;
@@ -0,0 +1,42 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import yaml from "js-yaml";
import { getInjectable } from "@ogre-tools/injectable";
import type { HelmReleaseUpdateDetails } from "../helm-releases.api";
import { apiBaseInjectionToken } from "../../api-base";
import { urlBuilderFor } from "../../../utils/buildUrl";
interface HelmReleaseCreatePayload {
name?: string;
repo: string;
chart: string;
namespace: string;
version: string;
values: string;
}
export type RequestCreateHelmRelease = (payload: HelmReleaseCreatePayload) => Promise<HelmReleaseUpdateDetails>;
const requestCreateEndpoint = urlBuilderFor("/v2/releases");
const requestCreateHelmReleaseInjectable = getInjectable({
id: "request-create-helm-release",
instantiate: (di): RequestCreateHelmRelease => {
const apiBase = di.inject(apiBaseInjectionToken);
return ({ repo, chart, values, ...data }) => {
return apiBase.post(requestCreateEndpoint.compile({}), {
data: {
chart: `${repo}/${chart}`,
values: yaml.load(values),
...data,
},
});
};
},
});
export default requestCreateHelmReleaseInjectable;
@@ -0,0 +1,22 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
export type RequestDeleteHelmRelease = (name: string, namespace: string) => Promise<void>;
const requestDeleteEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestDeleteHelmReleaseInjectable = getInjectable({
id: "request-delete-helm-release",
instantiate: (di): RequestDeleteHelmRelease => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, namespace) => apiBase.del(requestDeleteEndpoint.compile({ name, namespace }));
},
});
export default requestDeleteHelmReleaseInjectable;
@@ -0,0 +1,41 @@
/**
* 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 type { KubeJsonApiData } from "../../kube-json-api";
import { apiBaseInjectionToken } from "../../api-base";
import { urlBuilderFor } from "../../../utils/buildUrl";
export interface HelmReleaseDetails {
resources: KubeJsonApiData[];
name: string;
namespace: string;
version: string;
config: string; // release values
manifest: string;
info: {
deleted: string;
description: string;
first_deployed: string;
last_deployed: string;
notes: string;
status: string;
};
}
export type CallForHelmReleaseDetails = (name: string, namespace: string) => Promise<HelmReleaseDetails>;
const requestDetailsEnpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestHelmReleaseDetailsInjectable = getInjectable({
id: "call-for-helm-release-details",
instantiate: (di): CallForHelmReleaseDetails => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace }));
},
});
export default requestHelmReleaseDetailsInjectable;
@@ -0,0 +1,31 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
export interface HelmReleaseRevision {
revision: number;
updated: string;
status: string;
chart: string;
app_version: string;
description: string;
}
export type RequestHelmReleaseHistory = (name: string, namespace: string) => Promise<HelmReleaseRevision[]>;
const requestHistoryEnpoint = urlBuilderFor("/v2/releases/:namespace/:name/history");
const requestHelmReleaseHistoryInjectable = getInjectable({
id: "request-helm-release-history",
instantiate: (di): RequestHelmReleaseHistory => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, namespace) => apiBase.get(requestHistoryEnpoint.compile({ name, namespace }));
},
});
export default requestHelmReleaseHistoryInjectable;
@@ -0,0 +1,24 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
import type { HelmReleaseDto } from "../helm-releases.api";
export type RequestHelmReleases = (namespace?: string) => Promise<HelmReleaseDto[]>;
const requestHelmReleasesEndpoint = urlBuilderFor("/v2/releases/:namespace?");
const requestHelmReleasesInjectable = getInjectable({
id: "request-helm-releases",
instantiate: (di): RequestHelmReleases => {
const apiBase = di.inject(apiBaseInjectionToken);
return (namespace) => apiBase.get(requestHelmReleasesEndpoint.compile({ namespace }));
},
});
export default requestHelmReleasesInjectable;
@@ -0,0 +1,27 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
export type RequestHelmReleaseRollback = (name: string, namespace: string, revision: number) => Promise<void>;
const requestRollbackEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestHelmReleaseRollbackInjectable = getInjectable({
id: "request-helm-release-rollback",
instantiate: (di): RequestHelmReleaseRollback => {
const apiBase = di.inject(apiBaseInjectionToken);
return async (name, namespace, revision) => {
await apiBase.put(
requestRollbackEndpoint.compile({ name, namespace }),
{ data: { revision }},
);
};
},
});
export default requestHelmReleaseRollbackInjectable;
@@ -0,0 +1,49 @@
/**
* 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 { apiBaseInjectionToken } from "../../api-base";
import { urlBuilderFor } from "../../../utils/buildUrl";
interface HelmReleaseUpdatePayload {
repo: string;
chart: string;
version: string;
values: string;
}
export type RequestHelmReleaseUpdate = (
name: string,
namespace: string,
payload: HelmReleaseUpdatePayload
) => Promise<{ updateWasSuccessful: true } | { updateWasSuccessful: false; error: unknown }>;
const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
const requestHelmReleaseUpdateInjectable = getInjectable({
id: "request-helm-release-update",
instantiate: (di): RequestHelmReleaseUpdate => {
const apiBase = di.inject(apiBaseInjectionToken);
return async (name, namespace, { repo, chart, values, ...data }) => {
try {
await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), {
data: {
chart: `${repo}/${chart}`,
values: yaml.load(values),
...data,
},
});
} catch (e) {
return { updateWasSuccessful: false, error: e };
}
return { updateWasSuccessful: true };
};
},
});
export default requestHelmReleaseUpdateInjectable;
@@ -0,0 +1,22 @@
/**
* 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 { urlBuilderFor } from "../../../utils/buildUrl";
import { apiBaseInjectionToken } from "../../api-base";
export type RequestHelmReleaseValues = (name: string, namespace: string, all?: boolean) => Promise<string>;
const requestValuesEndpoint = urlBuilderFor("/v2/release/:namespace/:name/values");
const requestHelmReleaseValuesInjectable = getInjectable({
id: "request-helm-release-values",
instantiate: (di): RequestHelmReleaseValues => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, namespace, all) => apiBase.get(requestValuesEndpoint.compile({ name, namespace }, { all }));
},
});
export default requestHelmReleaseValuesInjectable;
+1
View File
@@ -34,6 +34,7 @@ export * from "./replica-set.api";
export * from "./resource-quota.api";
export * from "./role.api";
export * from "./role-binding.api";
export * from "./runtime-class.api";
export * from "./secret.api";
export * from "./self-subject-rules-reviews.api";
export * from "./service.api";
@@ -6,8 +6,6 @@
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isString, iter } from "../../utils";
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
@@ -24,26 +22,6 @@ export class IngressApi extends KubeApi<Ingress> {
}
}
export function getMetricsForIngress(ingress: string, namespace: string): Promise<IngressMetricData> {
const opts = { category: "ingress", ingress, namespace };
return metricsApi.getMetrics({
bytesSentSuccess: opts,
bytesSentFailure: opts,
requestDurationSeconds: opts,
responseDurationSeconds: opts,
}, {
namespace,
});
}
export interface IngressMetricData extends Partial<Record<string, MetricData>> {
bytesSentSuccess: MetricData;
bytesSentFailure: MetricData;
requestDurationSeconds: MetricData;
responseDurationSeconds: MetricData;
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;
+1 -19
View File
@@ -5,8 +5,7 @@
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData, PodSpec } from "./pod.api";
import type { PodSpec } from "./pod.api";
import type { Container } from "./types/container";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
@@ -103,20 +102,3 @@ export class JobApi extends KubeApi<Job> {
});
}
}
export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
+14 -77
View File
@@ -6,7 +6,7 @@
// Metrics api
import moment from "moment";
import { apiBase } from "../index";
import { isDefined, object } from "../../utils";
export interface MetricData {
status: string;
@@ -29,63 +29,6 @@ export interface MetricResult {
values: [number, string][];
}
export interface MetricProviderInfo {
name: string;
id: string;
isConfigurable: boolean;
}
export interface IMetricsReqParams {
start?: number | string; // timestamp in seconds or valid date-string
end?: number | string;
step?: number; // step in seconds (default: 60s = each point 1m)
range?: number; // time-range in seconds for data aggregation (default: 3600s = last 1h)
namespace?: string; // rbac-proxy validation param
}
export interface IResourceMetrics<T extends MetricData> {
[metric: string]: T;
cpuUsage: T;
memoryUsage: T;
fsUsage: T;
fsWrites: T;
fsReads: T;
networkReceive: T;
networkTransmit: T;
}
async function getMetrics(query: string, reqParams?: IMetricsReqParams): Promise<MetricData>;
async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise<MetricData[]>;
async function getMetrics<MetricNames extends string>(query: Record<MetricNames, Partial<Record<string, string>>>, reqParams?: IMetricsReqParams): Promise<Record<MetricNames, MetricData>>;
async function getMetrics(query: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, reqParams: IMetricsReqParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
const { range = 3600, step = 60, namespace } = reqParams;
let { start, end } = reqParams;
if (!start && !end) {
const timeNow = Date.now() / 1000;
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
start = now - range;
end = now;
}
return apiBase.post("/metrics", {
data: query,
query: {
start, end, step,
"kubernetes_namespace": namespace,
},
});
}
export const metricsApi = {
getMetrics,
async getMetricProviders(): Promise<MetricProviderInfo[]> {
return apiBase.get("/metrics/providers");
},
};
export function normalizeMetrics(metrics: MetricData | undefined | null, frames = 60): MetricData {
if (!metrics?.data?.result) {
return {
@@ -145,7 +88,7 @@ export function isMetricsEmpty(metrics: Partial<Record<string, MetricData>>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
}
export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | null | undefined, itemName: string): Partial<Record<string, MetricData>> | undefined {
export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys, MetricData>> | null | undefined, itemName: string): Partial<Record<Keys, MetricData>> | undefined {
if (!metrics) {
return undefined;
}
@@ -166,22 +109,16 @@ export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | nu
return itemMetrics;
}
export function getMetricLastPoints<T extends Partial<Record<string, MetricData>>>(metrics: T): Record<keyof T, number> {
const result: Partial<{ [metric: string]: number }> = {};
Object.keys(metrics).forEach(metricName => {
try {
const metric = metrics[metricName];
if (metric?.data.result.length) {
result[metricName] = +metric.data.result[0].values.slice(-1)[0][1];
}
} catch {
// ignore error
}
return result;
}, {});
return result as Record<keyof T, number>;
export function getMetricLastPoints<Keys extends string>(metrics: Partial<Record<Keys, MetricData>>): Partial<Record<Keys, number>> {
return object.fromEntries(
object.entries(metrics)
.map(([metricName, metric]) => {
try {
return [metricName, +metric.data.result[0].values.slice(-1)[0][1]] as const;
} catch {
return undefined;
}
})
.filter(isDefined),
);
}
@@ -0,0 +1,63 @@
/**
* 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 type { MetricData } from "../metrics.api";
import type { RequestMetricsParams } from "./request-metrics.injectable";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface ClusterMetricData {
memoryUsage: MetricData;
memoryRequests: MetricData;
memoryLimits: MetricData;
memoryCapacity: MetricData;
memoryAllocatableCapacity: MetricData;
cpuUsage: MetricData;
cpuRequests: MetricData;
cpuLimits: MetricData;
cpuCapacity: MetricData;
cpuAllocatableCapacity: MetricData;
podUsage: MetricData;
podCapacity: MetricData;
podAllocatableCapacity: MetricData;
fsSize: MetricData;
fsUsage: MetricData;
}
export type RequestClusterMetricsByNodeNames = (nodeNames: string[], params?: RequestMetricsParams) => Promise<ClusterMetricData>;
const requestClusterMetricsByNodeNamesInjectable = getInjectable({
id: "get-cluster-metrics-by-node-names",
instantiate: (di): RequestClusterMetricsByNodeNames => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (nodeNames, params) => {
const opts = {
category: "cluster",
nodes: nodeNames.join("|"),
};
return requestMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
cpuCapacity: opts,
cpuAllocatableCapacity: opts,
podUsage: opts,
podCapacity: opts,
podAllocatableCapacity: opts,
fsSize: opts,
fsUsage: opts,
}, params);
};
},
});
export default requestClusterMetricsByNodeNamesInjectable;
@@ -0,0 +1,38 @@
/**
* 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 type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface IngressMetricData {
bytesSentSuccess: MetricData;
bytesSentFailure: MetricData;
requestDurationSeconds: MetricData;
responseDurationSeconds: MetricData;
}
export type RequestIngressMetrics = (ingress: string, namespace: string) => Promise<IngressMetricData>;
const requestIngressMetricsInjectable = getInjectable({
id: "request-ingress-metrics",
instantiate: (di): RequestIngressMetrics => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (ingress, namespace) => {
const opts = { category: "ingress", ingress, namespace };
return requestMetrics({
bytesSentSuccess: opts,
bytesSentFailure: opts,
requestDurationSeconds: opts,
responseDurationSeconds: opts,
}, {
namespace,
});
};
},
});
export default requestIngressMetricsInjectable;
@@ -0,0 +1,44 @@
/**
* 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 type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface NodeMetricData {
memoryUsage: MetricData;
workloadMemoryUsage: MetricData;
memoryCapacity: MetricData;
memoryAllocatableCapacity: MetricData;
cpuUsage: MetricData;
cpuCapacity: MetricData;
fsUsage: MetricData;
fsSize: MetricData;
}
export type RequestAllNodeMetrics = () => Promise<NodeMetricData>;
const requestAllNodeMetricsInjectable = getInjectable({
id: "request-all-node-metrics",
instantiate: (di): RequestAllNodeMetrics => {
const requestMetrics = di.inject(requestMetricsInjectable);
return () => {
const opts = { category: "nodes" };
return requestMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuCapacity: opts,
fsSize: opts,
fsUsage: opts,
});
};
},
});
export default requestAllNodeMetricsInjectable;
@@ -0,0 +1,73 @@
/**
* 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 { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-time";
import { apiBaseInjectionToken } from "../../api-base";
import type { MetricData } from "../metrics.api";
export interface RequestMetricsParams {
/**
* timestamp in seconds or valid date-string
*/
start?: number | string;
/**
* timestamp in seconds or valid date-string
*/
end?: number | string;
/**
* step in seconds
* @default 60 (1 minute)
*/
step?: number;
/**
* time-range in seconds for data aggregation
* @default 3600 (1 hour)
*/
range?: number;
/**
* rbac-proxy validation param
*/
namespace?: string;
}
export interface RequestMetrics {
(query: string, params?: RequestMetricsParams): Promise<MetricData>;
(query: string[], params?: RequestMetricsParams): Promise<MetricData[]>;
<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
}
const requestMetricsInjectable = getInjectable({
id: "request-metrics",
instantiate: (di) => {
const apiBase = di.inject(apiBaseInjectionToken);
return (async (query: object, params: RequestMetricsParams = {}) => {
const { range = 3600, step = 60, namespace } = params;
let { start, end } = params;
if (!start && !end) {
const now = getSecondsFromUnixEpoch();
start = now - range;
end = now;
}
return apiBase.post("/metrics", {
data: query,
query: {
start, end, step,
"kubernetes_namespace": namespace,
},
});
}) as RequestMetrics;
},
});
export default requestMetricsInjectable;
@@ -0,0 +1,35 @@
/**
* 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 type { MetricData } from "../metrics.api";
import type { PersistentVolumeClaim } from "../persistent-volume-claim.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PersistentVolumeClaimMetricData {
diskUsage: MetricData;
diskCapacity: MetricData;
}
export type RequestPersistentVolumeClaimMetrics = (claim: PersistentVolumeClaim) => Promise<PersistentVolumeClaimMetricData>;
const requestPersistentVolumeClaimMetricsInjectable = getInjectable({
id: "request-persistent-volume-claim-metrics",
instantiate: (di): RequestPersistentVolumeClaimMetrics => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (claim) => {
const opts = { category: "pvc", pvc: claim.getName(), namespace: claim.getNs() };
return requestMetrics({
diskUsage: opts,
diskCapacity: opts,
}, {
namespace: opts.namespace,
});
};
},
});
export default requestPersistentVolumeClaimMetricsInjectable;
@@ -0,0 +1,46 @@
/**
* 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 type { DaemonSet } from "../daemon-set.api";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface DaemonSetPodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsForDaemonSets = (daemonsets: DaemonSet[], namespace: string, selector?: string) => Promise<DaemonSetPodMetricData>;
const requestPodMetricsForDaemonSetsInjectable = getInjectable({
id: "request-pod-metrics-for-daemon-sets",
instantiate: (di): RequestPodMetricsForDaemonSets => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (daemonSets, namespace, selector = "") => {
const podSelector = daemonSets.map(daemonSet => `${daemonSet.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsForDaemonSetsInjectable;
@@ -0,0 +1,46 @@
/**
* 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 type { Deployment } from "../deployment.api";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface DeploymentPodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsForDeployments = (deployments: Deployment[], namespace: string, selector?: string) => Promise<DeploymentPodMetricData>;
const requestPodMetricsForDeploymentsInjectable = getInjectable({
id: "request-pod-metrics-for-deployments",
instantiate: (di): RequestPodMetricsForDeployments => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (deployments, namespace, selector = "") => {
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsForDeploymentsInjectable;
@@ -0,0 +1,46 @@
/**
* 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 type { Job } from "../job.api";
import type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface JobPodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsForJobs = (jobs: Job[], namespace: string, selector?: string) => Promise<JobPodMetricData>;
const requestPodMetricsForJobsInjectable = getInjectable({
id: "request-pod-metrics-for-jobs",
instantiate: (di): RequestPodMetricsForJobs => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (jobs, namespace, selector) => {
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsForJobsInjectable;
@@ -0,0 +1,46 @@
/**
* 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 type { MetricData } from "../metrics.api";
import type { ReplicaSet } from "../replica-set.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface ReplicaSetPodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsForReplicaSets = (replicaSets: ReplicaSet[], namespace: string, selector?: string) => Promise<ReplicaSetPodMetricData>;
const requestPodMetricsForReplicaSetsInjectable = getInjectable({
id: "request-pod-metrics-for-replica-sets",
instantiate: (di): RequestPodMetricsForReplicaSets => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (replicaSets, namespace, selector = "") => {
const podSelector = replicaSets.map(replicaSet => `${replicaSet.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsForReplicaSetsInjectable;
@@ -0,0 +1,47 @@
/**
* 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 type { MetricData } from "../metrics.api";
import type { StatefulSet } from "../stateful-set.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface StatefulSetPodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsForStatefulSets = (statefulSets: StatefulSet[], namespace: string, selector?: string) => Promise<StatefulSetPodMetricData>;
const requestPodMetricsForStatefulSetsInjectable = getInjectable({
id: "request-pod-metrics-for-stateful-sets",
instantiate: (di): RequestPodMetricsForStatefulSets => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (statefulSets, namespace, selector = "") => {
const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsForStatefulSetsInjectable;
@@ -0,0 +1,44 @@
/**
* 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 type { MetricData } from "../metrics.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PodMetricInNamespaceData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
}
export type RequestPodMetricsInNamespace = (namespace: string, selector?: string) => Promise<PodMetricInNamespaceData>;
const requestPodMetricsInNamespaceInjectable = getInjectable({
id: "request-pod-metrics-in-namespace",
instantiate: (di): RequestPodMetricsInNamespace => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (namespace, selector) => {
const opts = { category: "pods", pods: ".*", namespace, selector };
return requestMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsInNamespaceInjectable;
@@ -0,0 +1,54 @@
/**
* 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 type { MetricData } from "../metrics.api";
import type { Pod } from "../pod.api";
import requestMetricsInjectable from "./request-metrics.injectable";
export interface PodMetricData {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
cpuRequests: MetricData;
cpuLimits: MetricData;
memoryRequests: MetricData;
memoryLimits: MetricData;
}
export type RequestPodMetrics = (pods: Pod[], namespace: string, selector?: string) => Promise<PodMetricData>;
const requestPodMetricsInjectable = getInjectable({
id: "request-pod-metrics",
instantiate: (di): RequestPodMetrics => {
const requestMetrics = di.inject(requestMetricsInjectable);
return (pods, namespace, selector = "pod, namespace") => {
const podSelector = pods.map(pod => pod.getName()).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return requestMetrics({
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
memoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
};
},
});
export default requestPodMetricsInjectable;
@@ -0,0 +1,26 @@
/**
* 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 { apiBaseInjectionToken } from "../../api-base";
export interface MetricProviderInfo {
name: string;
id: string;
isConfigurable: boolean;
}
export type RequestMetricsProviders = () => Promise<MetricProviderInfo[]>;
const requestMetricsProvidersInjectable = getInjectable({
id: "request-metrics-providers",
instantiate: (di): RequestMetricsProviders => {
const apiBase = di.inject(apiBaseInjectionToken);
return () => apiBase.get("/metrics/providers");
},
});
export default requestMetricsProvidersInjectable;
@@ -7,8 +7,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object";
import { KubeObject } from "../kube-object";
import { metricsApi } from "./metrics.api";
import type { PodMetricData } from "./pod.api";
export enum NamespaceStatusKind {
ACTIVE = "Active",
@@ -45,19 +43,3 @@ export class NamespaceApi extends KubeApi<Namespace> {
});
}
}
export function getMetricsForNamespace(namespace: string, selector = ""): Promise<PodMetricData> {
const opts = { category: "pods", pods: ".*", namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
-28
View File
@@ -6,8 +6,6 @@
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils";
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { TypedRegEx } from "typed-regex";
@@ -21,32 +19,6 @@ export class NodeApi extends KubeApi<Node> {
}
}
export function getMetricsForAllNodes(): Promise<NodeMetricData> {
const opts = { category: "nodes" };
return metricsApi.getMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuCapacity: opts,
fsSize: opts,
fsUsage: opts,
});
}
export interface NodeMetricData extends Partial<Record<string, MetricData>> {
memoryUsage: MetricData;
workloadMemoryUsage: MetricData;
memoryCapacity: MetricData;
memoryAllocatableCapacity: MetricData;
cpuUsage: MetricData;
cpuCapacity: MetricData;
fsUsage: MetricData;
fsSize: MetricData;
}
export interface NodeTaint {
key: string;
value?: string;
@@ -5,8 +5,6 @@
import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { Pod } from "./pod.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
@@ -22,22 +20,6 @@ export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
}
}
export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise<PersistentVolumeClaimMetricData> {
const opts = { category: "pvc", pvc: pvc.getName(), namespace: pvc.getNs() };
return metricsApi.getMetrics({
diskUsage: opts,
diskCapacity: opts,
}, {
namespace: opts.namespace,
});
}
export interface PersistentVolumeClaimMetricData extends Partial<Record<string, MetricData>> {
diskUsage: MetricData;
diskCapacity: MetricData;
}
export interface PersistentVolumeClaimSpec {
accessModes?: string[];
dataSource?: TypedLocalObjectReference;
+4 -37
View File
@@ -3,8 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
@@ -33,41 +31,6 @@ export class PodApi extends KubeApi<Pod> {
}
}
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<PodMetricData> {
const podSelector = pods.map(pod => pod.getName()).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
memoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface PodMetricData extends Partial<Record<string, MetricData>> {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
cpuRequests?: MetricData;
cpuLimits?: MetricData;
memoryRequests?: MetricData;
memoryLimits?: MetricData;
}
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
export interface PodLogsQuery {
container?: string;
@@ -714,6 +677,10 @@ export class Pod extends KubeObject<
return this.spec?.priorityClassName || "";
}
getRuntimeClassName() {
return this.spec?.runtimeClassName || "";
}
getServiceAccountName() {
return this.spec?.serviceAccountName || "";
}
@@ -5,8 +5,6 @@
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
@@ -41,23 +39,6 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
}
}
export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = replicasets.map(replicaset => `${replicaset.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface ReplicaSetSpec {
replicas?: number;
selector: LabelSelector;
@@ -1,38 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import yaml from "js-yaml";
import type { KubeJsonApiData } from "../kube-json-api";
import { apiBase } from "../index";
import type { Patch } from "rfc6902";
export const annotations = [
"kubectl.kubernetes.io/last-applied-configuration",
];
export async function update(resource: object | string): Promise<KubeJsonApiData> {
if (typeof resource === "string") {
const parsed = yaml.load(resource);
if (!parsed || typeof parsed !== "object") {
throw new Error("Cannot update resource to string or number");
}
resource = parsed;
}
return apiBase.post<KubeJsonApiData>("/stack", { data: resource });
}
export async function patch(name: string, kind: string, ns: string | undefined, patch: Patch): Promise<KubeJsonApiData> {
return apiBase.patch<KubeJsonApiData>("/stack", {
data: {
name,
kind,
ns,
patch,
},
});
}
@@ -0,0 +1,30 @@
/**
* 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 type { Patch } from "rfc6902";
import { apiBaseInjectionToken } from "../../api-base";
import type { KubeJsonApiData } from "../../kube-json-api";
export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise<KubeJsonApiData>;
const requestKubeObjectPatchInjectable = getInjectable({
id: "request-kube-object-patch",
instantiate: (di): RequestKubeObjectPatch => {
const apiBase = di.inject(apiBaseInjectionToken);
return (name, kind, ns, patch) => (
apiBase.patch("/stack", {
data: {
name,
kind,
ns,
patch,
},
})
);
},
});
export default requestKubeObjectPatchInjectable;
@@ -0,0 +1,20 @@
/**
* 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 { apiBaseInjectionToken } from "../../api-base";
import type { KubeJsonApiData } from "../../kube-json-api";
export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise<KubeJsonApiData>;
const requestKubeObjectCreationInjectable = getInjectable({
id: "request-kube-object-creation",
instantiate: (di): RequestKubeObjectCreation => {
const apiBase = di.inject(apiBaseInjectionToken);
return (data) => apiBase.post("/stack", { data });
},
});
export default requestKubeObjectCreationInjectable;
@@ -0,0 +1,22 @@
/**
* 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 assert from "assert";
import { storesAndApisCanBeCreatedInjectionToken } from "../stores-apis-can-be-created.token";
import { RuntimeClassApi } from "./runtime-class.api";
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
const runtimeClassApiInjectable = getInjectable({
id: "runtime-class-api",
instantiate: (di) => {
assert(di.inject(storesAndApisCanBeCreatedInjectionToken), "RuntimeClassApi is only available in certain environments");
return new RuntimeClassApi();
},
injectionToken: kubeApiInjectionToken,
});
export default runtimeClassApiInjectable;
@@ -0,0 +1,72 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api";
import type { ClusterScopedMetadata, KubeObjectMetadata, KubeObjectScope, Toleration } from "../kube-object";
import { KubeObject } from "../kube-object";
export interface RuntimeClassData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Cluster>, void, void> {
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
}
export interface RuntimeClassOverhead {
podFixed?: string;
}
export interface RuntimeClassScheduling {
nodeSelector?: Partial<Record<string, string>>;
tolerations?: Toleration[];
}
export class RuntimeClass extends KubeObject<
ClusterScopedMetadata,
void,
void
> {
static readonly kind = "RuntimeClass";
static readonly namespaced = false;
static readonly apiBase = "/apis/node.k8s.io/v1/runtimeclasses";
handler: string;
overhead?: RuntimeClassOverhead;
scheduling?: RuntimeClassScheduling;
constructor({ handler, overhead, scheduling, ...rest }: RuntimeClassData) {
super(rest);
this.handler = handler;
this.overhead = overhead;
this.scheduling = scheduling;
}
getHandler() {
return this.handler;
}
getPodFixed() {
return this.overhead?.podFixed ?? "";
}
getNodeSelectors(): string[] {
return Object.entries(this.scheduling?.nodeSelector ?? {})
.map(values => values.join(": "));
}
getTolerations() {
return this.scheduling?.tolerations ?? [];
}
}
export class RuntimeClassApi extends KubeApi<RuntimeClass, RuntimeClassData> {
constructor(opts: DerivedKubeApiOptions = {}) {
super({
objectConstructor: RuntimeClass,
...opts,
});
}
}
@@ -5,8 +5,6 @@
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData } from "./pod.api";
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import type { PodTemplateSpec } from "./types/pod-template-spec";
@@ -46,23 +44,6 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
}
}
export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface StatefulSetSpec {
serviceName: string;
replicas: number;
-7
View File
@@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export { apiBase } from "./api-base";
export { apiKube } from "./api-kube";
+11 -6
View File
@@ -9,12 +9,12 @@ import { Agent as HttpAgent } from "http";
import { Agent as HttpsAgent } from "https";
import { merge } from "lodash";
import type { Response, RequestInit } from "node-fetch";
import fetch from "node-fetch";
import { stringify } from "querystring";
import type { Patch } from "rfc6902";
import type { PartialDeep, ValueOf } from "type-fest";
import { EventEmitter } from "../../common/event-emitter";
import logger from "../../common/logger";
import type { Logger } from "../../common/logger";
import type { Fetch } from "../fetch/fetch.injectable";
import type { Defaulted } from "../utils";
import { json } from "../utils";
@@ -59,6 +59,11 @@ export type ParamsAndQuery<Params, Query> = (
: Params & { query?: undefined }
);
export interface JsonApiDependencies {
fetch: Fetch;
readonly logger: Logger;
}
export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>> {
static readonly reqInitDefault = {
headers: {
@@ -71,7 +76,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
debug: false,
};
constructor(public readonly config: JsonApiConfig, reqInit?: RequestInit) {
constructor(protected readonly dependencies: JsonApiDependencies, public readonly config: JsonApiConfig, reqInit?: RequestInit) {
this.config = Object.assign({}, JsonApi.configDefault, config);
this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit);
this.parseResponse = this.parseResponse.bind(this);
@@ -105,7 +110,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
}
return fetch(reqUrl, reqInit);
return this.dependencies.fetch(reqUrl, reqInit);
}
get<OutData = Data, Query = QueryParams>(
@@ -177,7 +182,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
reqInit,
};
const res = await fetch(reqUrl, reqInit);
const res = await this.dependencies.fetch(reqUrl, reqInit);
return this.parseResponse<OutData>(res, infoLog);
}
@@ -233,7 +238,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
protected writeLog(log: JsonApiLog) {
const { method, reqUrl, ...params } = log;
logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
this.dependencies.logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
}
}
Loaded 100 of 445 files, more files were not shown because too many files have changed in this diff. Show more