mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make PrometheusProviderRegistry fully injectable (#6592)
* Stop using source code in build file Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add new injectable version of binaryName Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add new NormalizedPlatform type Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch legacy execHelm to use legacy global DI for binaryPath Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove dead code Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce injectable for kube auth proxy certs Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce injectable forms of PrometheusProviders - Remove class requirement - Make everything injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update tests to not use private functions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Cleanup creating binary names and paths Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
30bfd6fa88
commit
286e6c8de7
@ -15,8 +15,8 @@ import type { SingleBar } from "cli-progress";
|
|||||||
import { MultiBar } from "cli-progress";
|
import { MultiBar } from "cli-progress";
|
||||||
import { extract } from "tar-stream";
|
import { extract } from "tar-stream";
|
||||||
import gunzip from "gunzip-maybe";
|
import gunzip from "gunzip-maybe";
|
||||||
import { getBinaryName } from "../src/common/vars";
|
|
||||||
import { isErrnoException, setTimeoutFor } from "../src/common/utils";
|
import { isErrnoException, setTimeoutFor } from "../src/common/utils";
|
||||||
|
import AbortController from "abort-controller";
|
||||||
|
|
||||||
type Response = FetchModule.Response;
|
type Response = FetchModule.Response;
|
||||||
type RequestInfo = FetchModule.RequestInfo;
|
type RequestInfo = FetchModule.RequestInfo;
|
||||||
@ -24,6 +24,14 @@ type RequestInit = FetchModule.RequestInit;
|
|||||||
|
|
||||||
const pipeline = promisify(_pipeline);
|
const pipeline = promisify(_pipeline);
|
||||||
|
|
||||||
|
const getBinaryName = (binaryName: string, { forPlatform }: { forPlatform : string }) => {
|
||||||
|
if (forPlatform === "windows") {
|
||||||
|
return `${binaryName}.exe`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return binaryName;
|
||||||
|
};
|
||||||
|
|
||||||
interface BinaryDownloaderArgs {
|
interface BinaryDownloaderArgs {
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
readonly platform: SupportedPlatform;
|
readonly platform: SupportedPlatform;
|
||||||
|
|||||||
24
src/common/utils/binary-name.injectable.ts
Normal file
24
src/common/utils/binary-name.injectable.ts
Normal file
@ -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, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import normalizedPlatformInjectable from "../vars/normalized-platform.injectable";
|
||||||
|
|
||||||
|
const binaryNameInjectable = getInjectable({
|
||||||
|
id: "binary-name",
|
||||||
|
instantiate: (di, binaryName) => {
|
||||||
|
const normalizedPlatform = di.inject(normalizedPlatformInjectable);
|
||||||
|
|
||||||
|
if (normalizedPlatform === "windows") {
|
||||||
|
return `${binaryName}.exe`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return binaryName;
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, binaryName: string) => binaryName,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default binaryNameInjectable;
|
||||||
24
src/common/utils/bundled-binary-path.injectable.ts
Normal file
24
src/common/utils/bundled-binary-path.injectable.ts
Normal file
@ -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, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||||
|
import baseBundledBinariesDirectoryInjectable from "../vars/base-bundled-binaries-dir.injectable";
|
||||||
|
import binaryNameInjectable from "./binary-name.injectable";
|
||||||
|
|
||||||
|
const bundledBinaryPathInjectable = getInjectable({
|
||||||
|
id: "bundled-binary-path",
|
||||||
|
instantiate: (di, name) => {
|
||||||
|
const joinPaths = di.inject(joinPathsInjectable);
|
||||||
|
const binaryName = di.inject(binaryNameInjectable, name);
|
||||||
|
const baseBundledBinariesDirectory = di.inject(baseBundledBinariesDirectoryInjectable);
|
||||||
|
|
||||||
|
return joinPaths(baseBundledBinariesDirectory, binaryName);
|
||||||
|
},
|
||||||
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, binaryName: string) => binaryName,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default bundledBinaryPathInjectable;
|
||||||
@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A OnceCell is an object that wraps some function that produces a value.
|
|
||||||
*
|
|
||||||
* It then only calls the function on the first call to `get()` and returns the
|
|
||||||
* same instance/value on every subsequent call.
|
|
||||||
*/
|
|
||||||
export interface LazyInitialized<T> {
|
|
||||||
get(): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function to make a `OnceCell<T>`
|
|
||||||
*/
|
|
||||||
export function lazyInitialized<T>(builder: () => T): LazyInitialized<T> {
|
|
||||||
let value: T | undefined;
|
|
||||||
let called = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
get() {
|
|
||||||
if (called) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
return value!;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = builder();
|
|
||||||
called = true;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -4,9 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
// App's common configuration for any process (main, renderer, build pipeline, etc.)
|
||||||
import path from "path";
|
|
||||||
import type { ThemeId } from "../renderer/themes/store";
|
import type { ThemeId } from "../renderer/themes/store";
|
||||||
import { lazyInitialized } from "./utils/lazy-initialized";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Switch to using isMacInjectable
|
* @deprecated Switch to using isMacInjectable
|
||||||
@ -48,73 +46,6 @@ export const defaultThemeId: ThemeId = "lens-dark";
|
|||||||
export const defaultFontSize = 12;
|
export const defaultFontSize = 12;
|
||||||
export const defaultTerminalFontFamily = "RobotoMono";
|
export const defaultTerminalFontFamily = "RobotoMono";
|
||||||
export const defaultEditorFontFamily = "RobotoMono";
|
export const defaultEditorFontFamily = "RobotoMono";
|
||||||
/**
|
|
||||||
* @deprecated use `di.inject(normalizedPlatformInjectable)` instead
|
|
||||||
*/
|
|
||||||
export const normalizedPlatform = (() => {
|
|
||||||
switch (process.platform) {
|
|
||||||
case "darwin":
|
|
||||||
return "darwin";
|
|
||||||
case "linux":
|
|
||||||
return "linux";
|
|
||||||
case "win32":
|
|
||||||
return "windows";
|
|
||||||
default:
|
|
||||||
throw new Error(`platform=${process.platform} is unsupported`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
/**
|
|
||||||
* @deprecated use `di.inject(bundledBinariesNormalizedArchInjectable)` instead
|
|
||||||
*/
|
|
||||||
export const normalizedArch = (() => {
|
|
||||||
switch (process.arch) {
|
|
||||||
case "arm64":
|
|
||||||
return "arm64";
|
|
||||||
case "x64":
|
|
||||||
case "amd64":
|
|
||||||
return "x64";
|
|
||||||
case "386":
|
|
||||||
case "x32":
|
|
||||||
case "ia32":
|
|
||||||
return "ia32";
|
|
||||||
default:
|
|
||||||
throw new Error(`arch=${process.arch} is unsupported`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
export function getBinaryName(name: string, { forPlatform = normalizedPlatform } = {}): string {
|
|
||||||
if (forPlatform === "windows") {
|
|
||||||
return `${name}.exe`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourcesDir = lazyInitialized(() => (
|
|
||||||
isProduction
|
|
||||||
? process.resourcesPath
|
|
||||||
: path.join(process.cwd(), "binaries", "client", normalizedPlatform)
|
|
||||||
));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated for being explicit side effect.
|
|
||||||
*/
|
|
||||||
export const baseBinariesDir = lazyInitialized(() => path.join(resourcesDir.get(), normalizedArch));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated for being explicit side effect.
|
|
||||||
*/
|
|
||||||
export const kubeAuthProxyBinaryName = getBinaryName("lens-k8s-proxy");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated for being explicit side effect.
|
|
||||||
*/
|
|
||||||
export const helmBinaryName = getBinaryName("helm");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated for being explicit side effect.
|
|
||||||
*/
|
|
||||||
export const helmBinaryPath = lazyInitialized(() => path.join(baseBinariesDir.get(), helmBinaryName));
|
|
||||||
|
|
||||||
// Apis
|
// Apis
|
||||||
export const apiPrefix = "/api"; // local router apis
|
export const apiPrefix = "/api"; // local router apis
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import platformInjectable from "./platform.injectable";
|
import platformInjectable from "./platform.injectable";
|
||||||
|
|
||||||
|
export type NormalizedPlatform = "darwin" | "linux" | "windows";
|
||||||
|
|
||||||
const normalizedPlatformInjectable = getInjectable({
|
const normalizedPlatformInjectable = getInjectable({
|
||||||
id: "normalized-platform",
|
id: "normalized-platform",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di): NormalizedPlatform => {
|
||||||
const platform = di.inject(platformInjectable);
|
const platform = di.inject(platformInjectable);
|
||||||
|
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
|
|||||||
@ -3,68 +3,43 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { UserStore } from "../../common/user-store";
|
|
||||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||||
import type { PrometheusService, PrometheusProviderRegistry } from "../prometheus";
|
|
||||||
import { PrometheusProvider } from "../prometheus";
|
|
||||||
import mockFs from "mock-fs";
|
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import prometheusProviderRegistryInjectable from "../prometheus/prometheus-provider-registry.injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
jest.mock("electron", () => ({
|
import type { PrometheusProvider } from "../prometheus/provider";
|
||||||
app: {
|
import { prometheusProviderInjectionToken } from "../prometheus/provider";
|
||||||
getVersion: () => "99.99.99",
|
import { runInAction } from "mobx";
|
||||||
getName: () => "lens",
|
|
||||||
setName: jest.fn(),
|
|
||||||
setPath: jest.fn(),
|
|
||||||
getPath: () => "tmp",
|
|
||||||
getLocale: () => "en",
|
|
||||||
setLoginItemSettings: jest.fn(),
|
|
||||||
},
|
|
||||||
ipcMain: {
|
|
||||||
on: jest.fn(),
|
|
||||||
handle: jest.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
enum ServiceResult {
|
enum ServiceResult {
|
||||||
Success,
|
Success,
|
||||||
Failure,
|
Failure,
|
||||||
Undefined,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestProvider extends PrometheusProvider {
|
const createTestPrometheusProvider = (kind: string, alwaysFail: ServiceResult): PrometheusProvider => ({
|
||||||
name = "TestProvider1";
|
kind,
|
||||||
rateAccuracy = "1h";
|
name: "TestProvider1",
|
||||||
isConfigurable = false;
|
isConfigurable: false,
|
||||||
|
getQuery: () => {
|
||||||
constructor(public id: string, public alwaysFail: ServiceResult) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
getQuery(): string {
|
|
||||||
throw new Error("getQuery is not implemented.");
|
throw new Error("getQuery is not implemented.");
|
||||||
}
|
},
|
||||||
|
getPrometheusService: async () => {
|
||||||
async getPrometheusService(): Promise<PrometheusService | undefined> {
|
switch (alwaysFail) {
|
||||||
switch (this.alwaysFail) {
|
|
||||||
case ServiceResult.Success:
|
case ServiceResult.Success:
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
kind,
|
||||||
namespace: "default",
|
namespace: "default",
|
||||||
port: 7000,
|
port: 7000,
|
||||||
service: "",
|
service: "",
|
||||||
};
|
};
|
||||||
case ServiceResult.Failure:
|
case ServiceResult.Failure:
|
||||||
throw new Error("does fail");
|
throw new Error("does fail");
|
||||||
case ServiceResult.Undefined:
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
|
|
||||||
const clusterStub = {
|
const clusterStub = {
|
||||||
getProxyKubeconfig: () => ({
|
getProxyKubeconfig: () => ({
|
||||||
@ -74,53 +49,34 @@ const clusterStub = {
|
|||||||
} as unknown as Cluster;
|
} as unknown as Cluster;
|
||||||
|
|
||||||
describe("ContextHandler", () => {
|
describe("ContextHandler", () => {
|
||||||
let createContextHandler: (cluster: Cluster) => ClusterContextHandler | undefined;
|
let createContextHandler: (cluster: Cluster) => ClusterContextHandler;
|
||||||
let prometheusProviderRegistry: PrometheusProviderRegistry;
|
let di: DiContainer;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||||
|
|
||||||
mockFs({
|
|
||||||
"tmp": {},
|
|
||||||
});
|
|
||||||
|
|
||||||
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
di.override(createKubeAuthProxyInjectable, () => ({} as any));
|
||||||
|
|
||||||
prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
|
||||||
|
|
||||||
createContextHandler = di.inject(createContextHandlerInjectable);
|
createContextHandler = di.inject(createContextHandlerInjectable);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
UserStore.resetInstance();
|
|
||||||
mockFs.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getPrometheusService", () => {
|
describe("getPrometheusService", () => {
|
||||||
it.each([
|
it.each([
|
||||||
[0, 0],
|
[0],
|
||||||
[0, 1],
|
[1],
|
||||||
[0, 2],
|
[2],
|
||||||
[0, 3],
|
[3],
|
||||||
])("should throw from %d success(es) after %d failure(s)", async (successes, failures) => {
|
])("should throw after %d failure(s)", async (failures) => {
|
||||||
let count = 0;
|
runInAction(() => {
|
||||||
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
for (let i = 0; i < failures; i += 1) {
|
||||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-failure-${i}`,
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (let i = 0; i < successes; i += 1) {
|
expect(() => createContextHandler(clusterStub).getPrometheusDetails()).rejects.toThrowError();
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
// TODO: Unit test shouldn't access protected or private methods
|
|
||||||
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };
|
|
||||||
|
|
||||||
return contextHandler.getPrometheusService();
|
|
||||||
}).rejects.toBeDefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -133,24 +89,27 @@ describe("ContextHandler", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[2, 3],
|
[2, 3],
|
||||||
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
|
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
|
||||||
let count = 0;
|
runInAction(() => {
|
||||||
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
for (let i = 0; i < failures; i += 1) {
|
||||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-failure-${i}`,
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < successes; i += 1) {
|
for (let i = 0; i < successes; i += 1) {
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-success-${i}`,
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: Unit test shouldn't access protected or private methods
|
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||||
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };
|
|
||||||
|
|
||||||
const service = await contextHandler.getPrometheusService();
|
expect(details.provider.kind === `id_failure_${failures}`);
|
||||||
|
|
||||||
expect(service.id === `id_${failures}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -163,24 +122,27 @@ describe("ContextHandler", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[2, 3],
|
[2, 3],
|
||||||
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
|
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
|
||||||
let count = 0;
|
runInAction(() => {
|
||||||
|
for (let i = 0; i < failures; i += 1) {
|
||||||
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-failure-${i}`,
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < successes; i += 1) {
|
for (let i = 0; i < successes; i += 1) {
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-success-${i}`,
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
|
||||||
|
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
expect(details.provider.kind === "id_failure_0");
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Unit test shouldn't access protected or private methods
|
|
||||||
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };
|
|
||||||
|
|
||||||
const service = await contextHandler.getPrometheusService();
|
|
||||||
|
|
||||||
expect(service.id === "id_0");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@ -193,45 +155,37 @@ describe("ContextHandler", () => {
|
|||||||
[2, 2],
|
[2, 2],
|
||||||
[2, 3],
|
[2, 3],
|
||||||
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
|
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
|
||||||
let count = 0;
|
|
||||||
const beforeSuccesses = Math.floor(successes / 2);
|
const beforeSuccesses = Math.floor(successes / 2);
|
||||||
const afterSuccesses = successes - beforeSuccesses;
|
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
for (let i = 0; i < beforeSuccesses; i += 1) {
|
for (let i = 0; i < beforeSuccesses; i += 1) {
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-success-${i}`,
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
for (let i = 0; i < failures; i += 1) {
|
||||||
const serviceResult = i % 2 === 0 ? ServiceResult.Failure : ServiceResult.Undefined;
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-failure-${i}`,
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, serviceResult));
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < afterSuccesses; i += 1) {
|
for (let i = beforeSuccesses; i < successes; i += 1) {
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
di.register(getInjectable({
|
||||||
|
id: `test-prometheus-provider-success-${i}`,
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Unit test shouldn't access protected or private methods
|
|
||||||
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };
|
|
||||||
|
|
||||||
const service = await contextHandler.getPrometheusService();
|
|
||||||
|
|
||||||
expect(service.id === "id_0");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shouldn't pick the second provider of 2 success(es) after 1 failure(s)", async () => {
|
const details = await createContextHandler(clusterStub).getPrometheusDetails();
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Failure));
|
expect(details.provider.kind === "id_success_0");
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
|
||||||
prometheusProviderRegistry.registerProvider(new TestProvider(`id_${count++}`, ServiceResult.Success));
|
|
||||||
|
|
||||||
// TODO: Unit test shouldn't access protected or private methods
|
|
||||||
const contextHandler = createContextHandler(clusterStub) as unknown as { getPrometheusService(): Promise<PrometheusService> };
|
|
||||||
|
|
||||||
const service = await contextHandler.getPrometheusService();
|
|
||||||
|
|
||||||
expect(service.id).not.toBe("id_2");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,16 +3,18 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PrometheusProvider, PrometheusService, PrometheusProviderRegistry } from "../prometheus/provider-registry";
|
import type { PrometheusProvider, PrometheusService } from "../prometheus/provider";
|
||||||
import type { ClusterPrometheusPreferences } from "../../common/cluster-types";
|
import type { ClusterPrometheusPreferences } from "../../common/cluster-types";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import type httpProxy from "http-proxy";
|
import type httpProxy from "http-proxy";
|
||||||
import type { UrlWithStringQuery } from "url";
|
import type { UrlWithStringQuery } from "url";
|
||||||
import url from "url";
|
import url from "url";
|
||||||
import { CoreV1Api } from "@kubernetes/client-node";
|
import { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import logger from "../logger";
|
|
||||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
||||||
import type { CreateKubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import type { CreateKubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import type { GetPrometheusProviderByKind } from "../prometheus/get-by-kind.injectable";
|
||||||
|
import type { IComputedValue } from "mobx";
|
||||||
|
import type { Logger } from "../../common/logger";
|
||||||
|
|
||||||
export interface PrometheusDetails {
|
export interface PrometheusDetails {
|
||||||
prometheusPath: string;
|
prometheusPath: string;
|
||||||
@ -26,10 +28,12 @@ interface PrometheusServicePreferences {
|
|||||||
prefix: string;
|
prefix: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
export interface ContextHandlerDependencies {
|
||||||
readonly createKubeAuthProxy: CreateKubeAuthProxy;
|
createKubeAuthProxy: CreateKubeAuthProxy;
|
||||||
|
getPrometheusProviderByKind: GetPrometheusProviderByKind;
|
||||||
readonly authProxyCa: string;
|
readonly authProxyCa: string;
|
||||||
readonly prometheusProviderRegistry: PrometheusProviderRegistry;
|
readonly prometheusProviders: IComputedValue<PrometheusProvider[]>;
|
||||||
|
readonly logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterContextHandler {
|
export interface ClusterContextHandler {
|
||||||
@ -51,7 +55,7 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
protected prometheusProvider?: string;
|
protected prometheusProvider?: string;
|
||||||
protected prometheus?: PrometheusServicePreferences;
|
protected prometheus?: PrometheusServicePreferences;
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies, protected cluster: Cluster) {
|
constructor(private readonly dependencies: ContextHandlerDependencies, protected readonly cluster: Cluster) {
|
||||||
this.clusterUrl = url.parse(cluster.apiUrl);
|
this.clusterUrl = url.parse(cluster.apiUrl);
|
||||||
this.setupPrometheus(cluster.preferences);
|
this.setupPrometheus(cluster.preferences);
|
||||||
}
|
}
|
||||||
@ -75,22 +79,21 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
|
|
||||||
protected ensurePrometheusProvider(service: PrometheusService): PrometheusProvider {
|
protected ensurePrometheusProvider(service: PrometheusService): PrometheusProvider {
|
||||||
if (!this.prometheusProvider) {
|
if (!this.prometheusProvider) {
|
||||||
logger.info(`[CONTEXT-HANDLER]: using ${service.id} as prometheus provider for clusterId=${this.cluster.id}`);
|
this.dependencies.logger.info(`[CONTEXT-HANDLER]: using ${service.kind} as prometheus provider for clusterId=${this.cluster.id}`);
|
||||||
this.prometheusProvider = service.id;
|
this.prometheusProvider = service.kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.dependencies.prometheusProviderRegistry.getByKind(this.prometheusProvider);
|
return this.dependencies.getPrometheusProviderByKind(this.prometheusProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected listPotentialProviders(): PrometheusProvider[] {
|
protected listPotentialProviders(): PrometheusProvider[] {
|
||||||
const registry = this.dependencies.prometheusProviderRegistry;
|
const provider = this.prometheusProvider && this.dependencies.getPrometheusProviderByKind(this.prometheusProvider);
|
||||||
const provider = this.prometheusProvider && registry.getByKind(this.prometheusProvider);
|
|
||||||
|
|
||||||
if (provider) {
|
if (provider) {
|
||||||
return [provider];
|
return [provider];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(registry.providers.values());
|
return this.dependencies.prometheusProviders.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getPrometheusService(): Promise<PrometheusService> {
|
protected async getPrometheusService(): Promise<PrometheusService> {
|
||||||
@ -98,7 +101,7 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
|
|
||||||
if (this.prometheus && this.prometheusProvider) {
|
if (this.prometheus && this.prometheusProvider) {
|
||||||
return {
|
return {
|
||||||
id: this.prometheusProvider,
|
kind: this.prometheusProvider,
|
||||||
namespace: this.prometheus.namespace,
|
namespace: this.prometheus.namespace,
|
||||||
service: this.prometheus.service,
|
service: this.prometheus.service,
|
||||||
port: this.prometheus.port,
|
port: this.prometheus.port,
|
||||||
|
|||||||
@ -3,32 +3,34 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import selfsigned from "selfsigned";
|
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import type { ClusterContextHandler } from "./context-handler";
|
import type { ClusterContextHandler, ContextHandlerDependencies } from "./context-handler";
|
||||||
import { ContextHandler } from "./context-handler";
|
import { ContextHandler } from "./context-handler";
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import { getKubeAuthProxyCertificate } from "../kube-auth-proxy/get-kube-auth-proxy-certificate";
|
import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable";
|
||||||
import URLParse from "url-parse";
|
import URLParse from "url-parse";
|
||||||
import prometheusProviderRegistryInjectable from "../prometheus/prometheus-provider-registry.injectable";
|
import getPrometheusProviderByKindInjectable from "../prometheus/get-by-kind.injectable";
|
||||||
|
import prometheusProvidersInjectable from "../prometheus/providers.injectable";
|
||||||
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
|
||||||
const createContextHandlerInjectable = getInjectable({
|
const createContextHandlerInjectable = getInjectable({
|
||||||
id: "create-context-handler",
|
id: "create-context-handler",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const createKubeAuthProxy = di.inject(createKubeAuthProxyInjectable);
|
const dependencies: Omit<ContextHandlerDependencies, "authProxyCa"> = {
|
||||||
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
|
||||||
|
getPrometheusProviderByKind: di.inject(getPrometheusProviderByKindInjectable),
|
||||||
|
prometheusProviders: di.inject(prometheusProvidersInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
};
|
||||||
|
|
||||||
return (cluster: Cluster): ClusterContextHandler => {
|
return (cluster: Cluster): ClusterContextHandler => {
|
||||||
const clusterUrl = new URLParse(cluster.apiUrl);
|
const clusterUrl = new URLParse(cluster.apiUrl);
|
||||||
|
|
||||||
const dependencies = {
|
return new ContextHandler({
|
||||||
createKubeAuthProxy,
|
...dependencies,
|
||||||
prometheusProviderRegistry,
|
authProxyCa: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname).cert,
|
||||||
authProxyCa: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate).cert,
|
}, cluster);
|
||||||
};
|
|
||||||
|
|
||||||
return new ContextHandler(dependencies, cluster);
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,9 +6,10 @@
|
|||||||
import { promiseExecFile } from "../../common/utils/promise-exec";
|
import { promiseExecFile } from "../../common/utils/promise-exec";
|
||||||
import type { ObjectEncodingOptions } from "fs";
|
import type { ObjectEncodingOptions } from "fs";
|
||||||
import type { ExecFileOptions, ExecFileOptionsWithStringEncoding } from "child_process";
|
import type { ExecFileOptions, ExecFileOptionsWithStringEncoding } from "child_process";
|
||||||
import { helmBinaryPath } from "../../common/vars";
|
|
||||||
import { UserStore } from "../../common/user-store";
|
import { UserStore } from "../../common/user-store";
|
||||||
import { isChildProcessError } from "../../common/utils";
|
import { isChildProcessError } from "../../common/utils";
|
||||||
|
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||||
|
import helmBinaryPathInjectable from "./helm-binary-path.injectable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExecFile the bundled helm CLI
|
* ExecFile the bundled helm CLI
|
||||||
@ -19,6 +20,8 @@ export async function execHelm(args: string[], { encoding, ...rest }: ObjectEnco
|
|||||||
encoding: encoding ?? "utf-8",
|
encoding: encoding ?? "utf-8",
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
const di = getLegacyGlobalDiForExtensionApi();
|
||||||
|
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const opts = { ...options };
|
const opts = { ...options };
|
||||||
@ -29,7 +32,7 @@ export async function execHelm(args: string[], { encoding, ...rest }: ObjectEnco
|
|||||||
opts.env.HTTPS_PROXY = UserStore.getInstance().httpsProxy;
|
opts.env.HTTPS_PROXY = UserStore.getInstance().httpsProxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout } = await promiseExecFile(helmBinaryPath.get(), args, opts);
|
const { stdout } = await promiseExecFile(helmBinaryPath, args, opts);
|
||||||
|
|
||||||
return stdout;
|
return stdout;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -3,23 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { getBinaryName } from "../../common/vars";
|
import bundledBinaryPathInjectable from "../../common/utils/bundled-binary-path.injectable";
|
||||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
|
||||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
|
||||||
|
|
||||||
const helmBinaryPathInjectable = getInjectable({
|
const helmBinaryPathInjectable = getInjectable({
|
||||||
id: "helm-binary-path",
|
id: "helm-binary-path",
|
||||||
|
instantiate: (di) => di.inject(bundledBinaryPathInjectable, "helm"),
|
||||||
instantiate: (di) => {
|
|
||||||
const joinPaths = di.inject(joinPathsInjectable);
|
|
||||||
const normalizedPlatform = di.inject(normalizedPlatformInjectable);
|
|
||||||
const baseBundledBinariesDirectory = di.inject(baseBundledBinariesDirectoryInjectable);
|
|
||||||
|
|
||||||
const helmBinaryName = getBinaryName("helm", { forPlatform: normalizedPlatform });
|
|
||||||
|
|
||||||
return joinPaths(baseBundledBinariesDirectory, helmBinaryName);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default helmBinaryPathInjectable;
|
export default helmBinaryPathInjectable;
|
||||||
|
|||||||
@ -6,14 +6,11 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
import { KubeAuthProxy } from "./kube-auth-proxy";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import selfsigned from "selfsigned";
|
|
||||||
import { getBinaryName } from "../../common/vars";
|
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import { getKubeAuthProxyCertificate } from "./get-kube-auth-proxy-certificate";
|
import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
|
||||||
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
import waitUntilPortIsUsedInjectable from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
import lensK8sProxyPathInjectable from "./lens-k8s-proxy-path.injectable";
|
||||||
|
|
||||||
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
export type CreateKubeAuthProxy = (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||||
|
|
||||||
@ -21,20 +18,20 @@ const createKubeAuthProxyInjectable = getInjectable({
|
|||||||
id: "create-kube-auth-proxy",
|
id: "create-kube-auth-proxy",
|
||||||
|
|
||||||
instantiate: (di): CreateKubeAuthProxy => {
|
instantiate: (di): CreateKubeAuthProxy => {
|
||||||
const binaryName = getBinaryName("lens-k8s-proxy");
|
const dependencies: Omit<KubeAuthProxyDependencies, "proxyCert"> = {
|
||||||
const joinPaths = di.inject(joinPathsInjectable);
|
proxyBinPath: di.inject(lensK8sProxyPathInjectable),
|
||||||
|
|
||||||
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
|
||||||
const clusterUrl = new URL(cluster.apiUrl);
|
|
||||||
const dependencies: KubeAuthProxyDependencies = {
|
|
||||||
proxyBinPath: joinPaths(di.inject(baseBundledBinariesDirectoryInjectable), binaryName),
|
|
||||||
proxyCert: getKubeAuthProxyCertificate(clusterUrl.hostname, selfsigned.generate),
|
|
||||||
spawn: di.inject(spawnInjectable),
|
spawn: di.inject(spawnInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
|
waitUntilPortIsUsed: di.inject(waitUntilPortIsUsedInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
return new KubeAuthProxy(dependencies, cluster, environmentVariables);
|
return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => {
|
||||||
|
const clusterUrl = new URL(cluster.apiUrl);
|
||||||
|
|
||||||
|
return new KubeAuthProxy({
|
||||||
|
...dependencies,
|
||||||
|
proxyCert: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname),
|
||||||
|
}, cluster, environmentVariables);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,15 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as selfsigned from "selfsigned";
|
import { generate } from "selfsigned";
|
||||||
import { getOrInsertWith } from "../../common/utils";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
type SelfSignedGenerate = typeof selfsigned.generate;
|
const kubeAuthProxyCertificateInjectable = getInjectable({
|
||||||
|
id: "kube-auth-proxy-certificate",
|
||||||
const certCache = new Map<string, selfsigned.SelfSignedCert>();
|
instantiate: (di, hostname) => generate(
|
||||||
|
|
||||||
export function getKubeAuthProxyCertificate(hostname: string, generate: SelfSignedGenerate): selfsigned.SelfSignedCert {
|
|
||||||
return getOrInsertWith(certCache, hostname, () => generate(
|
|
||||||
[
|
[
|
||||||
{ name: "commonName", value: "Lens Certificate Authority" },
|
{ name: "commonName", value: "Lens Certificate Authority" },
|
||||||
{ name: "organizationName", value: "Lens" },
|
{ name: "organizationName", value: "Lens" },
|
||||||
@ -31,5 +28,11 @@ export function getKubeAuthProxyCertificate(hostname: string, generate: SelfSign
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
));
|
),
|
||||||
}
|
lifecycle: lifecycleEnum.keyedSingleton({
|
||||||
|
getInstanceKey: (di, hostname: string) => hostname,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default kubeAuthProxyCertificateInjectable;
|
||||||
|
|
||||||
13
src/main/kube-auth-proxy/lens-k8s-proxy-path.injectable.ts
Normal file
13
src/main/kube-auth-proxy/lens-k8s-proxy-path.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 bundledBinaryPathInjectable from "../../common/utils/bundled-binary-path.injectable";
|
||||||
|
|
||||||
|
const lensK8sProxyPathInjectable = getInjectable({
|
||||||
|
id: "lens-k8s-proxy-path",
|
||||||
|
instantiate: (di) => di.inject(bundledBinaryPathInjectable, "lens-k8s-proxy"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default lensK8sProxyPathInjectable;
|
||||||
@ -3,17 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
import binaryNameInjectable from "../../common/utils/binary-name.injectable";
|
||||||
|
|
||||||
const kubectlBinaryNameInjectable = getInjectable({
|
const kubectlBinaryNameInjectable = getInjectable({
|
||||||
id: "kubectl-binary-name",
|
id: "kubectl-binary-name",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => di.inject(binaryNameInjectable, "kubectl"),
|
||||||
const platform = di.inject(normalizedPlatformInjectable);
|
|
||||||
|
|
||||||
return platform === "windows"
|
|
||||||
? "kubectl.exe"
|
|
||||||
: "kubectl";
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default kubectlBinaryNameInjectable;
|
export default kubectlBinaryNameInjectable;
|
||||||
|
|||||||
@ -3,20 +3,11 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import joinPathsInjectable from "../../common/path/join-paths.injectable";
|
import bundledBinaryPathInjectable from "../../common/utils/bundled-binary-path.injectable";
|
||||||
import baseBundledBinariesDirectoryInjectable from "../../common/vars/base-bundled-binaries-dir.injectable";
|
|
||||||
import kubectlBinaryNameInjectable from "./binary-name.injectable";
|
|
||||||
|
|
||||||
const bundledKubectlBinaryPathInjectable = getInjectable({
|
const bundledKubectlBinaryPathInjectable = getInjectable({
|
||||||
id: "bundled-kubectl-binary-path",
|
id: "bundled-kubectl-binary-path",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => di.inject(bundledBinaryPathInjectable, "kubectl"),
|
||||||
const joinPaths = di.inject(joinPathsInjectable);
|
|
||||||
|
|
||||||
return joinPaths(
|
|
||||||
di.inject(baseBundledBinariesDirectoryInjectable),
|
|
||||||
di.inject(kubectlBinaryNameInjectable),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default bundledKubectlBinaryPathInjectable;
|
export default bundledKubectlBinaryPathInjectable;
|
||||||
|
|||||||
@ -17,12 +17,13 @@ import { noop } from "lodash/fp";
|
|||||||
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
import type { JoinPaths } from "../../common/path/join-paths.injectable";
|
||||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||||
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
import type { GetBasenameOfPath } from "../../common/path/get-basename.injectable";
|
||||||
|
import type { NormalizedPlatform } from "../../common/vars/normalized-platform.injectable";
|
||||||
|
|
||||||
const initScriptVersionString = "# lens-initscript v3";
|
const initScriptVersionString = "# lens-initscript v3";
|
||||||
|
|
||||||
export interface KubectlDependencies {
|
export interface KubectlDependencies {
|
||||||
readonly directoryForKubectlBinaries: string;
|
readonly directoryForKubectlBinaries: string;
|
||||||
readonly normalizedDownloadPlatform: "darwin" | "linux" | "windows";
|
readonly normalizedDownloadPlatform: NormalizedPlatform;
|
||||||
readonly normalizedDownloadArch: "amd64" | "arm64" | "386";
|
readonly normalizedDownloadArch: "amd64" | "arm64" | "386";
|
||||||
readonly kubectlBinaryName: string;
|
readonly kubectlBinaryName: string;
|
||||||
readonly bundledKubectlBinaryPath: string;
|
readonly bundledKubectlBinaryPath: string;
|
||||||
|
|||||||
29
src/main/prometheus/get-by-kind.injectable.ts
Normal file
29
src/main/prometheus/get-by-kind.injectable.ts
Normal file
@ -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 { matches } from "lodash/fp";
|
||||||
|
import type { PrometheusProvider } from "./provider";
|
||||||
|
import prometheusProvidersInjectable from "./providers.injectable";
|
||||||
|
|
||||||
|
export type GetPrometheusProviderByKind = (kind: string) => PrometheusProvider;
|
||||||
|
|
||||||
|
const getPrometheusProviderByKindInjectable = getInjectable({
|
||||||
|
id: "get-prometheus-provider-by-kind",
|
||||||
|
instantiate: (di): GetPrometheusProviderByKind => {
|
||||||
|
const providers = di.inject(prometheusProvidersInjectable);
|
||||||
|
|
||||||
|
return (kind) => {
|
||||||
|
const provider = providers.get().find(matches({ kind }));
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
throw new Error(`Provider of kind "${kind}" does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default getPrometheusProviderByKindInjectable;
|
||||||
23
src/main/prometheus/helm-14-provider.injectable.ts
Normal file
23
src/main/prometheus/helm-14-provider.injectable.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getLensLikeQueryFor } from "./lens-provider.injectable";
|
||||||
|
import { createPrometheusProvider, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
const helm14PrometheusProviderInjectable = getInjectable({
|
||||||
|
id: "helm14-prometheus-provider",
|
||||||
|
instantiate: () => createPrometheusProvider({
|
||||||
|
kind: "helm14",
|
||||||
|
name: "Helm 14.x",
|
||||||
|
isConfigurable: true,
|
||||||
|
getQuery: getLensLikeQueryFor({ rateAccuracy: "5m" }),
|
||||||
|
getService: (client) => findFirstNamespacedService(client, "app=prometheus,component=server,heritage=Helm"),
|
||||||
|
}),
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default helm14PrometheusProviderInjectable;
|
||||||
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { PrometheusLens } from "./lens";
|
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
|
||||||
import type { PrometheusService } from "./provider-registry";
|
|
||||||
import { isRequestError } from "../../common/utils";
|
|
||||||
|
|
||||||
export class PrometheusHelm14 extends PrometheusLens {
|
|
||||||
readonly id: string = "helm14";
|
|
||||||
readonly name: string = "Helm 14.x";
|
|
||||||
readonly rateAccuracy: string = "5m";
|
|
||||||
readonly isConfigurable: boolean = true;
|
|
||||||
|
|
||||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
|
||||||
try {
|
|
||||||
const selector = "app=prometheus,component=server,heritage=Helm";
|
|
||||||
const { body: { items: [service] }} = await client.listServiceForAllNamespaces(undefined, undefined, undefined, selector);
|
|
||||||
|
|
||||||
if (service?.metadata?.namespace && service.metadata.name && service.spec?.ports && service.metadata?.labels?.chart?.startsWith("prometheus-14")) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
namespace: service.metadata.namespace,
|
|
||||||
service: service.metadata.name,
|
|
||||||
port: service.spec.ports[0].port,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to list services for Prometheus ${this.name} in all namespaces: ${isRequestError(error) ? error.response?.body.message : error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`No service found for Prometheus ${this.name} from any namespace`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,20 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
import type { PrometheusProvider } from "./provider";
|
||||||
import { inspect } from "util";
|
import { createPrometheusProvider, bytesSent, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||||
import { PrometheusProvider, type PrometheusService } from "./provider-registry";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
export class PrometheusHelm extends PrometheusProvider {
|
|
||||||
readonly id: string = "helm";
|
|
||||||
readonly name: string = "Helm";
|
|
||||||
readonly rateAccuracy: string = "5m";
|
|
||||||
readonly isConfigurable: boolean = true;
|
|
||||||
|
|
||||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
export const getHelmLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||||
return this.getFirstNamespacedService(client, "app=prometheus,component=server,heritage=Helm");
|
(opts, queryName) => {
|
||||||
}
|
|
||||||
|
|
||||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
|
||||||
switch(opts.category) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
@ -33,7 +25,7 @@ export class PrometheusHelm extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${rateAccuracy}]))`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -65,7 +57,7 @@ export class PrometheusHelm extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${rateAccuracy}])) by(node)`;
|
||||||
case "cpuCapacity":
|
case "cpuCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "cpuAllocatableCapacity":
|
case "cpuAllocatableCapacity":
|
||||||
@ -79,7 +71,7 @@ export class PrometheusHelm extends PrometheusProvider {
|
|||||||
case "pods":
|
case "pods":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -93,13 +85,13 @@ export class PrometheusHelm extends PrometheusProvider {
|
|||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "fsWrites":
|
case "fsWrites":
|
||||||
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "fsReads":
|
case "fsReads":
|
||||||
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkReceive":
|
case "networkReceive":
|
||||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkTransmit":
|
case "networkTransmit":
|
||||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pvc":
|
case "pvc":
|
||||||
@ -113,17 +105,42 @@ export class PrometheusHelm extends PrometheusProvider {
|
|||||||
case "ingress":
|
case "ingress":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "bytesSentSuccess":
|
case "bytesSentSuccess":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^2\\\\d*",
|
||||||
|
});
|
||||||
case "bytesSentFailure":
|
case "bytesSentFailure":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^5\\\\d*",
|
||||||
|
});
|
||||||
case "requestDurationSeconds":
|
case "requestDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
case "responseDurationSeconds":
|
case "responseDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const helmPrometheusProviderInjectable = getInjectable({
|
||||||
|
id: "helm-prometheus-provider",
|
||||||
|
instantiate: () => createPrometheusProvider({
|
||||||
|
kind: "helm",
|
||||||
|
name: "Helm",
|
||||||
|
isConfigurable: true,
|
||||||
|
getQuery: getHelmLikeQueryFor({ rateAccuracy: "5m" }),
|
||||||
|
getService: (client) => findFirstNamespacedService(client, "app=prometheus,component=server,heritage=Helm"),
|
||||||
|
}),
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default helmPrometheusProviderInjectable;
|
||||||
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from "./provider-registry";
|
|
||||||
@ -3,22 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PrometheusService } from "./provider-registry";
|
import { bytesSent, prometheusProviderInjectionToken, findNamespacedService, createPrometheusProvider } from "./provider";
|
||||||
import { PrometheusProvider } from "./provider-registry";
|
import type { PrometheusProvider } from "./provider";
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { inspect } from "util";
|
|
||||||
|
|
||||||
export class PrometheusLens extends PrometheusProvider {
|
export const getLensLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||||
readonly id: string = "lens";
|
(opts, queryName) => {
|
||||||
readonly name: string = "Lens";
|
|
||||||
readonly rateAccuracy: string = "1m";
|
|
||||||
readonly isConfigurable: boolean = false;
|
|
||||||
|
|
||||||
public getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
|
||||||
return this.getNamespacedService(client, "prometheus", "lens-metrics");
|
|
||||||
}
|
|
||||||
|
|
||||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
|
||||||
switch(opts.category) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
@ -35,7 +25,7 @@ export class PrometheusLens extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
return `sum(rate(node_cpu_seconds_total{kubernetes_node=~"${opts.nodes}", mode=~"user|system"}[${rateAccuracy}]))`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -67,7 +57,7 @@ export class PrometheusLens extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(kubernetes_node)`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${rateAccuracy}])) by(kubernetes_node)`;
|
||||||
case "cpuCapacity":
|
case "cpuCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "cpuAllocatableCapacity":
|
case "cpuAllocatableCapacity":
|
||||||
@ -81,7 +71,7 @@ export class PrometheusLens extends PrometheusProvider {
|
|||||||
case "pods":
|
case "pods":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -95,13 +85,13 @@ export class PrometheusLens extends PrometheusProvider {
|
|||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "fsWrites":
|
case "fsWrites":
|
||||||
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "fsReads":
|
case "fsReads":
|
||||||
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkReceive":
|
case "networkReceive":
|
||||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkTransmit":
|
case "networkTransmit":
|
||||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pvc":
|
case "pvc":
|
||||||
@ -115,17 +105,42 @@ export class PrometheusLens extends PrometheusProvider {
|
|||||||
case "ingress":
|
case "ingress":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "bytesSentSuccess":
|
case "bytesSentSuccess":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^2\\\\d*",
|
||||||
|
});
|
||||||
case "bytesSentFailure":
|
case "bytesSentFailure":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^5\\\\d*",
|
||||||
|
});
|
||||||
case "requestDurationSeconds":
|
case "requestDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
case "responseDurationSeconds":
|
case "responseDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const lensPrometheusProviderInjectable = getInjectable({
|
||||||
|
id: "lens-prometheus-provider",
|
||||||
|
instantiate: () => createPrometheusProvider({
|
||||||
|
kind: "lens",
|
||||||
|
name: "Lens",
|
||||||
|
isConfigurable: false,
|
||||||
|
getQuery: getLensLikeQueryFor({ rateAccuracy: "1m" }),
|
||||||
|
getService: (client) => findNamespacedService(client, "prometheus", "lens-metrics"),
|
||||||
|
}),
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default lensPrometheusProviderInjectable;
|
||||||
|
|
||||||
@ -3,22 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PrometheusService } from "./provider-registry";
|
import type { PrometheusProvider } from "./provider";
|
||||||
import { PrometheusProvider } from "./provider-registry";
|
import { bytesSent, createPrometheusProvider, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { inspect } from "util";
|
|
||||||
|
|
||||||
export class PrometheusOperator extends PrometheusProvider {
|
export const getOperatorLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||||
readonly rateAccuracy: string = "1m";
|
(opts, queryName) => {
|
||||||
readonly id: string = "operator";
|
|
||||||
readonly name: string = "Prometheus Operator";
|
|
||||||
readonly isConfigurable: boolean = true;
|
|
||||||
|
|
||||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
|
||||||
return this.getFirstNamespacedService(client, "operated-prometheus=true");
|
|
||||||
}
|
|
||||||
|
|
||||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
|
||||||
switch(opts.category) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
@ -35,7 +25,7 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"})`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"})`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${rateAccuracy}])* on (pod,namespace) group_left(node) kube_pod_info{node=~"${opts.nodes}"})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -67,7 +57,7 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}]) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${rateAccuracy}]) * on (pod, namespace) group_left(node) kube_pod_info) by (node)`;
|
||||||
case "cpuCapacity":
|
case "cpuCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "cpuAllocatableCapacity":
|
case "cpuAllocatableCapacity":
|
||||||
@ -81,7 +71,7 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "pods":
|
case "pods":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(container_cpu_usage_seconds_total{container!="", image!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_cpu_usage_seconds_total{container!="", image!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}", resource="cpu", namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}", resource="cpu", namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -95,13 +85,13 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(container_fs_usage_bytes{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(container_fs_usage_bytes{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "fsWrites":
|
case "fsWrites":
|
||||||
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "fsReads":
|
case "fsReads":
|
||||||
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkReceive":
|
case "networkReceive":
|
||||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkTransmit":
|
case "networkTransmit":
|
||||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pvc":
|
case "pvc":
|
||||||
@ -115,17 +105,42 @@ export class PrometheusOperator extends PrometheusProvider {
|
|||||||
case "ingress":
|
case "ingress":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "bytesSentSuccess":
|
case "bytesSentSuccess":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^2\\\\d*",
|
||||||
|
});
|
||||||
case "bytesSentFailure":
|
case "bytesSentFailure":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^5\\\\d*",
|
||||||
|
});
|
||||||
case "requestDurationSeconds":
|
case "requestDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
case "responseDurationSeconds":
|
case "responseDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const operatorPrometheusProviderInjectable = getInjectable({
|
||||||
|
id: "operator-prometheus-provider",
|
||||||
|
instantiate: () => createPrometheusProvider({
|
||||||
|
kind: "operator",
|
||||||
|
name: "Prometheus Operator",
|
||||||
|
isConfigurable: true,
|
||||||
|
getService: (client) => findFirstNamespacedService(client, "operated-prometheus=true"),
|
||||||
|
getQuery: getOperatorLikeQueryFor({ rateAccuracy: "1m" }),
|
||||||
|
}),
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default operatorPrometheusProviderInjectable;
|
||||||
|
|
||||||
@ -1,13 +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 { PrometheusProviderRegistry } from "./provider-registry";
|
|
||||||
|
|
||||||
const prometheusProviderRegistryInjectable = getInjectable({
|
|
||||||
id: "prometheus-provider-registry",
|
|
||||||
instantiate: () => new PrometheusProviderRegistry(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default prometheusProviderRegistryInjectable;
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
|
||||||
import { isRequestError } from "../../common/utils";
|
|
||||||
|
|
||||||
export interface PrometheusService {
|
|
||||||
id: string;
|
|
||||||
namespace: string;
|
|
||||||
service: string;
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class PrometheusProvider {
|
|
||||||
abstract readonly id: string;
|
|
||||||
abstract readonly name: string;
|
|
||||||
abstract readonly rateAccuracy: string;
|
|
||||||
abstract readonly isConfigurable: boolean;
|
|
||||||
|
|
||||||
abstract getQuery(opts: Record<string, string>, queryName: string): string;
|
|
||||||
abstract getPrometheusService(client: CoreV1Api): Promise<PrometheusService | undefined>;
|
|
||||||
|
|
||||||
protected bytesSent(ingress: string, namespace: string, statuses: string): string {
|
|
||||||
return `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getFirstNamespacedService(client: CoreV1Api, ...selectors: string[]): Promise<PrometheusService> {
|
|
||||||
try {
|
|
||||||
for (const selector of selectors) {
|
|
||||||
const { body: { items: [service] }} = await client.listServiceForAllNamespaces(undefined, undefined, undefined, selector);
|
|
||||||
|
|
||||||
if (service?.metadata?.namespace && service.metadata.name && service.spec?.ports) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
namespace: service.metadata.namespace,
|
|
||||||
service: service.metadata.name,
|
|
||||||
port: service.spec.ports[0].port,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to list services for Prometheus${this.name} in all namespaces: ${isRequestError(error) ? error.response?.body.message : error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`No service found for Prometheus${this.name} from any namespace`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getNamespacedService(client: CoreV1Api, name: string, namespace: string): Promise<PrometheusService> {
|
|
||||||
try {
|
|
||||||
const { body: service } = await client.readNamespacedService(name, namespace);
|
|
||||||
|
|
||||||
if (!service.metadata?.namespace || !service.metadata.name || !service.spec?.ports) {
|
|
||||||
throw new Error(`Service returned from Prometheus${this.name} in namespace="${namespace}" did not have required information`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
namespace: service.metadata.namespace,
|
|
||||||
service: service.metadata.name,
|
|
||||||
port: service.spec.ports[0].port,
|
|
||||||
};
|
|
||||||
} catch(error) {
|
|
||||||
throw new Error(`Failed to list services for Prometheus${this.name} in namespace="${namespace}": ${isRequestError(error) ? error.response?.body.message : error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PrometheusProviderRegistry {
|
|
||||||
public providers = new Map<string, PrometheusProvider>();
|
|
||||||
|
|
||||||
getByKind(kind: string): PrometheusProvider {
|
|
||||||
const provider = this.providers.get(kind);
|
|
||||||
|
|
||||||
if (!provider) {
|
|
||||||
throw new Error("Unknown Prometheus provider");
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerProvider(provider: PrometheusProvider): this {
|
|
||||||
if (this.providers.has(provider.id)) {
|
|
||||||
throw new Error("Provider already registered under that kind");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.providers.set(provider.id, provider);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
103
src/main/prometheus/provider.ts
Normal file
103
src/main/prometheus/provider.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { CoreV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
import { isRequestError } from "../../common/utils";
|
||||||
|
|
||||||
|
export interface PrometheusService extends PrometheusServiceInfo {
|
||||||
|
kind: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrometheusServiceInfo {
|
||||||
|
namespace: string;
|
||||||
|
service: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrometheusProvider {
|
||||||
|
readonly kind: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly isConfigurable: boolean;
|
||||||
|
|
||||||
|
getQuery(opts: Record<string, string>, queryName: string): string;
|
||||||
|
getPrometheusService(client: CoreV1Api): Promise<PrometheusService>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePrometheusProviderOpts {
|
||||||
|
readonly kind: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly isConfigurable: boolean;
|
||||||
|
|
||||||
|
getQuery(opts: Record<string, string>, queryName: string): string;
|
||||||
|
getService(client: CoreV1Api): Promise<PrometheusServiceInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createPrometheusProvider = ({ getService, ...opts }: CreatePrometheusProviderOpts): PrometheusProvider => ({
|
||||||
|
...opts,
|
||||||
|
getPrometheusService: async (client) => {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
kind: opts.kind,
|
||||||
|
...await getService(client),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to find Prometheus provider for "${opts.name}"`, { cause: error });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function findFirstNamespacedService(client: CoreV1Api, ...selectors: string[]): Promise<PrometheusServiceInfo> {
|
||||||
|
try {
|
||||||
|
for (const selector of selectors) {
|
||||||
|
const { body: { items: [service] }} = await client.listServiceForAllNamespaces(undefined, undefined, undefined, selector);
|
||||||
|
|
||||||
|
if (service?.metadata?.namespace && service.metadata.name && service.spec?.ports) {
|
||||||
|
return {
|
||||||
|
namespace: service.metadata.namespace,
|
||||||
|
service: service.metadata.name,
|
||||||
|
port: service.spec.ports[0].port,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to list services in all namespaces: ${isRequestError(error) ? error.response?.body.message : error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`No service found from any namespace`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findNamespacedService(client: CoreV1Api, name: string, namespace: string): Promise<PrometheusServiceInfo> {
|
||||||
|
try {
|
||||||
|
const { body: service } = await client.readNamespacedService(name, namespace);
|
||||||
|
|
||||||
|
if (!service.metadata?.namespace || !service.metadata.name || !service.spec?.ports) {
|
||||||
|
throw new Error(`Service found in namespace="${namespace}" did not have required information`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
namespace: service.metadata.namespace,
|
||||||
|
service: service.metadata.name,
|
||||||
|
port: service.spec.ports[0].port,
|
||||||
|
};
|
||||||
|
} catch(error) {
|
||||||
|
throw new Error(`Failed to list services in namespace="${namespace}": ${isRequestError(error) ? error.response?.body.message : error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BytesSentArgs {
|
||||||
|
rateAccuracy: string;
|
||||||
|
ingress: string;
|
||||||
|
namespace: string;
|
||||||
|
statuses: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bytesSent({ rateAccuracy, ingress, namespace, statuses }: BytesSentArgs): string {
|
||||||
|
return `sum(rate(nginx_ingress_controller_bytes_sent_sum{ingress="${ingress}",namespace="${namespace}",status=~"${statuses}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prometheusProviderInjectionToken = getInjectionToken<PrometheusProvider>({
|
||||||
|
id: "prometheus-provider",
|
||||||
|
});
|
||||||
18
src/main/prometheus/providers.injectable.ts
Normal file
18
src/main/prometheus/providers.injectable.ts
Normal file
@ -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 { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx";
|
||||||
|
import { prometheusProviderInjectionToken } from "./provider";
|
||||||
|
|
||||||
|
const prometheusProvidersInjectable = getInjectable({
|
||||||
|
id: "prometheus-providers",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const computedInjectMany = di.inject(computedInjectManyInjectable);
|
||||||
|
|
||||||
|
return computedInjectMany(prometheusProviderInjectionToken);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default prometheusProvidersInjectable;
|
||||||
@ -3,22 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PrometheusService } from "./provider-registry";
|
import type { PrometheusProvider } from "./provider";
|
||||||
import { PrometheusProvider } from "./provider-registry";
|
import { bytesSent, createPrometheusProvider, findFirstNamespacedService, prometheusProviderInjectionToken } from "./provider";
|
||||||
import type { CoreV1Api } from "@kubernetes/client-node";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { inspect } from "util";
|
|
||||||
|
|
||||||
export class PrometheusStacklight extends PrometheusProvider {
|
export const getStacklightLikeQueryFor = ({ rateAccuracy }: { rateAccuracy: string }): PrometheusProvider["getQuery"] => (
|
||||||
readonly id: string = "stacklight";
|
(opts, queryName) => {
|
||||||
readonly name: string = "Stacklight";
|
|
||||||
readonly rateAccuracy: string = "1m";
|
|
||||||
readonly isConfigurable: boolean = true;
|
|
||||||
|
|
||||||
public getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
|
||||||
return this.getNamespacedService(client, "prometheus-server", "stacklight");
|
|
||||||
}
|
|
||||||
|
|
||||||
public getQuery(opts: Record<string, string>, queryName: string): string {
|
|
||||||
switch(opts.category) {
|
switch(opts.category) {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
@ -35,7 +25,7 @@ export class PrometheusStacklight extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
return `sum(kube_node_status_allocatable{node=~"${opts.nodes}", resource="memory"}) by (component)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${this.rateAccuracy}]))`;
|
return `sum(rate(node_cpu_seconds_total{node=~"${opts.nodes}", mode=~"user|system"}[${rateAccuracy}]))`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
return `sum(kube_pod_container_resource_requests{node=~"${opts.nodes}", resource="cpu"}) by (component)`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -67,7 +57,7 @@ export class PrometheusStacklight extends PrometheusProvider {
|
|||||||
case "memoryAllocatableCapacity":
|
case "memoryAllocatableCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="memory"}) by (node)`;
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${this.rateAccuracy}])) by(node)`;
|
return `sum(rate(node_cpu_seconds_total{mode=~"user|system"}[${rateAccuracy}])) by(node)`;
|
||||||
case "cpuCapacity":
|
case "cpuCapacity":
|
||||||
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
return `sum(kube_node_status_allocatable{resource="cpu"}) by (node)`;
|
||||||
case "cpuAllocatableCapacity":
|
case "cpuAllocatableCapacity":
|
||||||
@ -81,7 +71,7 @@ export class PrometheusStacklight extends PrometheusProvider {
|
|||||||
case "pods":
|
case "pods":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "cpuUsage":
|
case "cpuUsage":
|
||||||
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_cpu_usage_seconds_total{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "cpuRequests":
|
case "cpuRequests":
|
||||||
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(kube_pod_container_resource_requests{pod=~"${opts.pods}",resource="cpu",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "cpuLimits":
|
case "cpuLimits":
|
||||||
@ -95,13 +85,13 @@ export class PrometheusStacklight extends PrometheusProvider {
|
|||||||
case "fsUsage":
|
case "fsUsage":
|
||||||
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
return `sum(container_fs_usage_bytes{container!="POD",container!="",pod=~"${opts.pods}",namespace="${opts.namespace}"}) by (${opts.selector})`;
|
||||||
case "fsWrites":
|
case "fsWrites":
|
||||||
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_writes_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "fsReads":
|
case "fsReads":
|
||||||
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_fs_reads_bytes_total{container!="", pod=~"${opts.pods}", namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkReceive":
|
case "networkReceive":
|
||||||
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_receive_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
case "networkTransmit":
|
case "networkTransmit":
|
||||||
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (${opts.selector})`;
|
return `sum(rate(container_network_transmit_bytes_total{pod=~"${opts.pods}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (${opts.selector})`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pvc":
|
case "pvc":
|
||||||
@ -115,17 +105,42 @@ export class PrometheusStacklight extends PrometheusProvider {
|
|||||||
case "ingress":
|
case "ingress":
|
||||||
switch (queryName) {
|
switch (queryName) {
|
||||||
case "bytesSentSuccess":
|
case "bytesSentSuccess":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^2\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^2\\\\d*",
|
||||||
|
});
|
||||||
case "bytesSentFailure":
|
case "bytesSentFailure":
|
||||||
return this.bytesSent(opts.ingress, opts.namespace, "^5\\\\d*");
|
return bytesSent({
|
||||||
|
rateAccuracy,
|
||||||
|
ingress: opts.ingress,
|
||||||
|
namespace: opts.namespace,
|
||||||
|
statuses: "^5\\\\d*",
|
||||||
|
});
|
||||||
case "requestDurationSeconds":
|
case "requestDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_request_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
case "responseDurationSeconds":
|
case "responseDurationSeconds":
|
||||||
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${this.rateAccuracy}])) by (ingress, namespace)`;
|
return `sum(rate(nginx_ingress_controller_response_duration_seconds_sum{ingress="${opts.ingress}",namespace="${opts.namespace}"}[${rateAccuracy}])) by (ingress, namespace)`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unknown query name ${inspect(queryName, false, undefined, false)} for category: ${inspect(opts.category, false, undefined, false)}`);
|
throw new Error(`Unknown queryName="${queryName}" for category="${opts.category}"`);
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
const stacklightPrometheusProviderInjectable = getInjectable({
|
||||||
|
id: "stacklight-prometheus-provider",
|
||||||
|
instantiate: () => createPrometheusProvider({
|
||||||
|
kind: "stacklight",
|
||||||
|
name: "Stacklight",
|
||||||
|
isConfigurable: true,
|
||||||
|
getService: (client) => findFirstNamespacedService(client, "prometheus-server", "stacklight"),
|
||||||
|
getQuery: getStacklightLikeQueryFor({ rateAccuracy: "1m" }),
|
||||||
|
}),
|
||||||
|
injectionToken: prometheusProviderInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default stacklightPrometheusProviderInjectable;
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ const addMetricsRouteInjectable = getRouteInjectable({
|
|||||||
try {
|
try {
|
||||||
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
|
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
|
||||||
|
|
||||||
prometheusMetadata.provider = provider?.id;
|
prometheusMetadata.provider = provider?.kind;
|
||||||
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
|
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
|
||||||
|
|
||||||
if (!prometheusPath) {
|
if (!prometheusPath) {
|
||||||
|
|||||||
@ -6,23 +6,22 @@
|
|||||||
import { apiPrefix } from "../../../common/vars";
|
import { apiPrefix } from "../../../common/vars";
|
||||||
import { getRouteInjectable } from "../../router/router.injectable";
|
import { getRouteInjectable } from "../../router/router.injectable";
|
||||||
import { route } from "../../router/route";
|
import { route } from "../../router/route";
|
||||||
import prometheusProviderRegistryInjectable from "../../prometheus/prometheus-provider-registry.injectable";
|
import prometheusProvidersInjectable from "../../prometheus/providers.injectable";
|
||||||
|
|
||||||
const getMetricProvidersRouteInjectable = getRouteInjectable({
|
const getMetricProvidersRouteInjectable = getRouteInjectable({
|
||||||
id: "get-metric-providers-route",
|
id: "get-metric-providers-route",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
const prometheusProviders = di.inject(prometheusProvidersInjectable);
|
||||||
|
|
||||||
return route({
|
return route({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: `${apiPrefix}/metrics/providers`,
|
path: `${apiPrefix}/metrics/providers`,
|
||||||
})(() => ({
|
})(() => ({
|
||||||
response: Array.from(
|
response: (
|
||||||
prometheusProviderRegistry
|
prometheusProviders
|
||||||
.providers
|
.get()
|
||||||
.values(),
|
.map(({ name, kind: id, isConfigurable }) => ({ name, id, isConfigurable }))
|
||||||
({ name, id, isConfigurable }) => ({ name, id, isConfigurable }),
|
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,36 +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 { PrometheusLens } from "../../prometheus/lens";
|
|
||||||
import { PrometheusHelm } from "../../prometheus/helm";
|
|
||||||
import { PrometheusHelm14 } from "../../prometheus/helm-14";
|
|
||||||
import { PrometheusOperator } from "../../prometheus/operator";
|
|
||||||
import { PrometheusStacklight } from "../../prometheus/stacklight";
|
|
||||||
import prometheusProviderRegistryInjectable from "../../prometheus/prometheus-provider-registry.injectable";
|
|
||||||
import { onLoadOfApplicationInjectionToken } from "../runnable-tokens/on-load-of-application-injection-token";
|
|
||||||
|
|
||||||
const setupPrometheusRegistryInjectable = getInjectable({
|
|
||||||
id: "setup-prometheus-registry",
|
|
||||||
|
|
||||||
instantiate: (di) => {
|
|
||||||
const prometheusProviderRegistry = di.inject(prometheusProviderRegistryInjectable);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: "setup-prometheus-registry",
|
|
||||||
run: () => {
|
|
||||||
prometheusProviderRegistry
|
|
||||||
.registerProvider(new PrometheusLens())
|
|
||||||
.registerProvider(new PrometheusHelm14())
|
|
||||||
.registerProvider(new PrometheusHelm())
|
|
||||||
.registerProvider(new PrometheusOperator())
|
|
||||||
.registerProvider(new PrometheusStacklight());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
injectionToken: onLoadOfApplicationInjectionToken,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default setupPrometheusRegistryInjectable;
|
|
||||||
Loading…
Reference in New Issue
Block a user