mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Making apiBase injectable (#6022)
* Making apiBase injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all of Helm functions to be DI Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make PortForward's use of apiBase fully injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all metric requests to be injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace resource applier with injectables Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch KubeJsonApi.forCluster to be injectable but do not use Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert the rest of shell sessions to be DI-ed - This is a prerequesit for using the new createKubeJsonApiForClusterInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Use new createKubeJsonApiForClusterInjectable for openNodeShellSession Signed-off-by: Sebastian Malton <sebastian@malton.name> * Make KubeconfigDialog injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove jest-fetch-mock and make fetch injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests with new global override Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add new injectable for create KubeJsonApi and JsonApi instances Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix showing-details-for-helm-release behavioural tests - Remove HelmChartStore in favour of all injectables - Create a model for UpgradeChartDockTab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix show details and updating helm releases tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix residual typing issues related to metrics Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash on load due to circular dependency Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix create resource tab not working Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove legacy apiBase global Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use isDebuggingInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use windowLocationInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove global legacy apiKube Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve injectable filenames compared to the injectables inside Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove modifying input in requestActivePortForwardInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Introduce and use get(Milli)SecondsFromUnixEpochInjectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Switch to non-reactive way of gettting possible helm release versions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix typo Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bug in KubeApi constructor Signed-off-by: Sebastian Malton <sebastian@malton.name> * Convert all KubeApi related tests to use asyncFn Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests after introducing new injectables that have side effects Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix bad rebase causing tests to fail Signed-off-by: Sebastian Malton <sebastian@malton.name> * Improve expects for multiple field values Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix crash will looking up api refs Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix breaking change on KubeApi.list Signed-off-by: Sebastian Malton <sebastian@malton.name> * Better fix for formatting urls Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove injectable for time since we should just use useMockTime Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add happy path behavioural tests for upgrade chart tab Signed-off-by: Sebastian Malton <sebastian@malton.name> * Remove debug message Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * fix showing-details-for-helm-release tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix installing-helm-chart-from-new-tab tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests relating to hosted cluster id Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots to recent changes in master Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> * Reupdated upgrade chart new tab test snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness in unit test when using <Animated> Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix flakiness and improve tests for DeleteClusterDialog Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix kubeconfig-sync tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix <Extensions> tests by removing mockFs and making everything injectable Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix build issues Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix getElectronAppPathInjectable override not returning absolute paths - Also fixes the listing-active-helm-repos-in-prefs tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Replace all uses of getAbsolutePath with joinPaths as it is more correct and less confusing Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix opening application window tests by making override properly absolute Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots relating no longer using getAbsolutePath Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix and add behavioural tests for RenderDelay Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix test flakiness because of path side effects, propagate uses to as many places Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix extension-discovery tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Add global override to fix some tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Rewrite and fix implementation of KubeconfigManager and its tests Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests by global override pathExists Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix unit tests failing on windows by using injectable verions of path functions Signed-off-by: Sebastian Malton <sebastian@malton.name> * Attempt to fix test timeout by using runInAction Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix tests after rebase Signed-off-by: Sebastian Malton <sebastian@malton.name> * Fix setupIpcMainHandlers usage Signed-off-by: Sebastian Malton <sebastian@malton.name> * Update snapshots Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Sebastian Malton <sebastian@malton.name> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com> Co-authored-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
parent
ed073d6562
commit
76066c5ebf
@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mock the global window variable
|
||||
*/
|
||||
export function mockWindow() {
|
||||
Object.defineProperty(window, "requestIdleCallback", {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation(callback => callback()),
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "cancelIdleCallback", {
|
||||
writable: true,
|
||||
value: jest.fn(),
|
||||
});
|
||||
}
|
||||
@ -351,7 +351,7 @@
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/sharp": "^0.31.0",
|
||||
"@types/spdy": "^3.4.5",
|
||||
"@types/tar": "^4.0.5",
|
||||
"@types/tar": "^6.1.2",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/tempy": "^0.3.0",
|
||||
@ -396,7 +396,6 @@
|
||||
"jest": "^28.1.3",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-mock-extended": "^2.0.9",
|
||||
"make-plural": "^6.2.2",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForBinariesInjectable = getInjectable({
|
||||
id: "directory-for-binaries",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(directoryForUserData, "binaries");
|
||||
return joinPaths(directoryForUserData, "binaries");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -4,19 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForKubeConfigsInjectable = getInjectable({
|
||||
id: "directory-for-kube-configs",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(
|
||||
directoryForUserData,
|
||||
"kubeconfigs",
|
||||
);
|
||||
return joinPaths(directoryForUserData, "kubeconfigs");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -4,17 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForBinariesInjectable from "../directory-for-binaries/directory-for-binaries.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const directoryForKubectlBinariesInjectable = getInjectable({
|
||||
id: "directory-for-kubectl-binaries",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForBinaries = di.inject(directoryForBinariesInjectable);
|
||||
|
||||
|
||||
return getAbsolutePath(directoryForBinaries, "kubectl");
|
||||
return joinPaths(directoryForBinaries, "kubectl");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -4,17 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForKubeConfigsInjectable from "../directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||
import getAbsolutePathInjectable from "../../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../../path/join-paths.injectable";
|
||||
|
||||
const getCustomKubeConfigDirectoryInjectable = getInjectable({
|
||||
id: "get-custom-kube-config-directory",
|
||||
|
||||
instantiate: (di) => {
|
||||
const directoryForKubeConfigs = di.inject(directoryForKubeConfigsInjectable);
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (directoryName: string) =>
|
||||
getAbsolutePath(directoryForKubeConfigs, directoryName);
|
||||
return (directoryName: string) => joinPaths(directoryForKubeConfigs, directoryName);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
18
src/common/catalog/filtered-categories.injectable.ts
Normal file
18
src/common/catalog/filtered-categories.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 { computed } from "mobx";
|
||||
import catalogCategoryRegistryInjectable from "./category-registry.injectable";
|
||||
|
||||
const filteredCategoriesInjectable = getInjectable({
|
||||
id: "filtered-categories",
|
||||
instantiate: (di) => {
|
||||
const registry = di.inject(catalogCategoryRegistryInjectable);
|
||||
|
||||
return computed(() => [...registry.filteredItems]);
|
||||
},
|
||||
});
|
||||
|
||||
export default filteredCategoriesInjectable;
|
||||
@ -9,7 +9,6 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { HttpError } from "@kubernetes/client-node";
|
||||
import type { Kubectl } from "../../main/kubectl/kubectl";
|
||||
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
|
||||
import { loadConfigFromFile } from "../kube-helpers";
|
||||
import type { KubeApiResource, KubeResource } from "../rbac";
|
||||
import { apiResourceRecord, apiResources } from "../rbac";
|
||||
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
|
||||
@ -25,6 +24,7 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
|
||||
import assert from "assert";
|
||||
import type { Logger } from "../logger";
|
||||
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
|
||||
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
|
||||
|
||||
export interface ClusterDependencies {
|
||||
readonly directoryForKubeConfigs: string;
|
||||
@ -37,6 +37,7 @@ export interface ClusterDependencies {
|
||||
createListNamespaces: (config: KubeConfig) => ListNamespaces;
|
||||
createVersionDetector: (cluster: Cluster) => VersionDetector;
|
||||
broadcastMessage: BroadcastMessage;
|
||||
loadConfigfromFile: LoadConfigfromFile;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -500,7 +501,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
}
|
||||
|
||||
async getKubeconfig(): Promise<KubeConfig> {
|
||||
const { config } = await loadConfigFromFile(this.kubeConfigPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
@ -510,7 +511,7 @@ export class Cluster implements ClusterModel, ClusterState {
|
||||
*/
|
||||
async getProxyKubeconfig(): Promise<KubeConfig> {
|
||||
const proxyKCPath = await this.getProxyKubeconfigPath();
|
||||
const { config } = await loadConfigFromFile(proxyKCPath);
|
||||
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import getAbsolutePathInjectable from "../path/get-absolute-path.injectable";
|
||||
import joinPathsInjectable from "../path/join-paths.injectable";
|
||||
|
||||
const directoryForLensLocalStorageInjectable = getInjectable({
|
||||
id: "directory-for-lens-local-storage",
|
||||
|
||||
instantiate: (di) => {
|
||||
const getAbsolutePath = di.inject(getAbsolutePathInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
|
||||
return getAbsolutePath(
|
||||
return joinPaths(
|
||||
directoryForUserData,
|
||||
"lens-local-storage",
|
||||
);
|
||||
|
||||
11
src/common/fetch/fetch.global-override-for-injectable.ts
Normal file
11
src/common/fetch/fetch.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import fetchInjectable from "./fetch.injectable";
|
||||
|
||||
export default getGlobalOverride(fetchInjectable, () => () => {
|
||||
throw new Error("tried to fetch a resource without override in test");
|
||||
});
|
||||
17
src/common/fetch/fetch.injectable.ts
Normal file
17
src/common/fetch/fetch.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { RequestInfo, RequestInit, Response } from "node-fetch";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
|
||||
|
||||
const fetchInjectable = getInjectable({
|
||||
id: "fetch",
|
||||
instantiate: (): Fetch => fetch,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default fetchInjectable;
|
||||
11
src/common/fs/access-path.global-override-for-injectable.ts
Normal file
11
src/common/fs/access-path.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import accessPathInjectable from "./access-path.injectable";
|
||||
|
||||
export default getGlobalOverride(accessPathInjectable, () => async () => {
|
||||
throw new Error("tried to verify path access without override");
|
||||
});
|
||||
27
src/common/fs/access-path.injectable.ts
Normal file
27
src/common/fs/access-path.injectable.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type AccessPath = (path: string, mode?: number) => Promise<boolean>;
|
||||
|
||||
const accessPathInjectable = getInjectable({
|
||||
id: "access-path",
|
||||
instantiate: (di): AccessPath => {
|
||||
const { access } = di.inject(fsInjectable);
|
||||
|
||||
return async (path, mode) => {
|
||||
try {
|
||||
await access(path, mode);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default accessPathInjectable;
|
||||
11
src/common/fs/copy.global-override-for-injectable.ts
Normal file
11
src/common/fs/copy.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import copyInjectable from "./copy.injectable";
|
||||
|
||||
export default getGlobalOverride(copyInjectable, () => async () => {
|
||||
throw new Error("tried to copy filepaths without override");
|
||||
});
|
||||
16
src/common/fs/copy.injectable.ts
Normal file
16
src/common/fs/copy.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { CopyOptions } from "fs-extra";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Copy = (src: string, dest: string, options?: CopyOptions | undefined) => Promise<void>;
|
||||
|
||||
const copyInjectable = getInjectable({
|
||||
id: "copy",
|
||||
instantiate: (di): Copy => di.inject(fsInjectable).copy,
|
||||
});
|
||||
|
||||
export default copyInjectable;
|
||||
11
src/common/fs/delete-file.global-override-for-injectable.ts
Normal file
11
src/common/fs/delete-file.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import deleteFileInjectable from "./delete-file.injectable";
|
||||
|
||||
export default getGlobalOverride(deleteFileInjectable, () => async () => {
|
||||
throw new Error("tried to delete file without override");
|
||||
});
|
||||
15
src/common/fs/delete-file.injectable.ts
Normal file
15
src/common/fs/delete-file.injectable.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type DeleteFile = (filePath: string) => Promise<void>;
|
||||
|
||||
const deleteFileInjectable = getInjectable({
|
||||
id: "delete-file",
|
||||
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
|
||||
});
|
||||
|
||||
export default deleteFileInjectable;
|
||||
11
src/common/fs/ensure-dir.global-override-for-injectable.ts
Normal file
11
src/common/fs/ensure-dir.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import ensureDirInjectable from "./ensure-dir.injectable";
|
||||
|
||||
export default getGlobalOverride(ensureDirInjectable, () => async () => {
|
||||
throw new Error("tried to ensure directory without override");
|
||||
});
|
||||
@ -5,14 +5,14 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type EnsureDirectory = (dirPath: string) => Promise<void>;
|
||||
|
||||
const ensureDirInjectable = getInjectable({
|
||||
id: "ensure-dir",
|
||||
|
||||
// TODO: Remove usages of ensureDir from business logic.
|
||||
// TODO: Read, Write, Watch etc. operations should do this internally.
|
||||
instantiate: (di) => di.inject(fsInjectable).ensureDir,
|
||||
|
||||
causesSideEffects: true,
|
||||
instantiate: (di): EnsureDirectory => di.inject(fsInjectable).ensureDir,
|
||||
});
|
||||
|
||||
export default ensureDirInjectable;
|
||||
|
||||
11
src/common/fs/extract-tar.global-override-for-injectable.ts
Normal file
11
src/common/fs/extract-tar.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import extractTarInjectable from "./extract-tar.injectable";
|
||||
|
||||
export default getGlobalOverride(extractTarInjectable, () => async () => {
|
||||
throw new Error("tried to extract a tar file without override");
|
||||
});
|
||||
26
src/common/fs/extract-tar.injectable.ts
Normal file
26
src/common/fs/extract-tar.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ExtractOptions } from "tar";
|
||||
import { extract } from "tar";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
|
||||
export type ExtractTar = (filePath: string, opts?: ExtractOptions) => Promise<void>;
|
||||
|
||||
const extractTarInjectable = getInjectable({
|
||||
id: "extract-tar",
|
||||
instantiate: (di): ExtractTar => {
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return (filePath, opts = {}) => extract({
|
||||
file: filePath,
|
||||
cwd: getDirnameOfPath(filePath),
|
||||
...opts,
|
||||
});
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default extractTarInjectable;
|
||||
11
src/common/fs/lstat.global-override-for-injectable.ts
Normal file
11
src/common/fs/lstat.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import lstatInjectable from "./lstat.injectable";
|
||||
|
||||
export default getGlobalOverride(lstatInjectable, () => async () => {
|
||||
throw new Error("tried to lstat a filepath without override");
|
||||
});
|
||||
16
src/common/fs/lstat.injectable.ts
Normal file
16
src/common/fs/lstat.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Stats } from "fs";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type LStat = (path: string) => Promise<Stats>;
|
||||
|
||||
const lstatInjectable = getInjectable({
|
||||
id: "lstat",
|
||||
instantiate: (di): LStat => di.inject(fsInjectable).lstat,
|
||||
});
|
||||
|
||||
export default lstatInjectable;
|
||||
11
src/common/fs/move.global-override-for-injectable.ts
Normal file
11
src/common/fs/move.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import moveInjectable from "./move.injectable";
|
||||
|
||||
export default getGlobalOverride(moveInjectable, () => async () => {
|
||||
throw new Error("tried to move without override");
|
||||
});
|
||||
16
src/common/fs/move.injectable.ts
Normal file
16
src/common/fs/move.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MoveOptions } from "fs-extra";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type Move = (src: string, dest: string, options?: MoveOptions) => Promise<void>;
|
||||
|
||||
const moveInjectable = getInjectable({
|
||||
id: "move",
|
||||
instantiate: (di): Move => di.inject(fsInjectable).move,
|
||||
});
|
||||
|
||||
export default moveInjectable;
|
||||
11
src/common/fs/path-exists.global-override-for-injectable.ts
Normal file
11
src/common/fs/path-exists.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import pathExistsInjectable from "./path-exists.injectable";
|
||||
|
||||
export default getGlobalOverride(pathExistsInjectable, () => async () => {
|
||||
throw new Error("Tried to check if a path exists without override");
|
||||
});
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import readDirectoryInjectable from "./read-directory.injectable";
|
||||
|
||||
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
|
||||
throw new Error("tried to read a directory's content without override");
|
||||
});
|
||||
37
src/common/fs/read-directory.injectable.ts
Normal file
37
src/common/fs/read-directory.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Dirent } from "fs";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export interface ReadDirectory {
|
||||
(
|
||||
path: string,
|
||||
options: "buffer" | { encoding: "buffer"; withFileTypes?: false | undefined }
|
||||
): Promise<Buffer[]>;
|
||||
(
|
||||
path: string,
|
||||
options?:
|
||||
| { encoding: BufferEncoding | string | null; withFileTypes?: false | undefined }
|
||||
| BufferEncoding
|
||||
| string
|
||||
| null,
|
||||
): Promise<string[]>;
|
||||
(
|
||||
path: string,
|
||||
options?: { encoding?: BufferEncoding | string | null | undefined; withFileTypes?: false | undefined },
|
||||
): Promise<string[] | Buffer[]>;
|
||||
(
|
||||
path: string,
|
||||
options: { encoding?: BufferEncoding | string | null | undefined; withFileTypes: true },
|
||||
): Promise<Dirent[]>;
|
||||
}
|
||||
|
||||
const readDirectoryInjectable = getInjectable({
|
||||
id: "read-directory",
|
||||
instantiate: (di): ReadDirectory => di.inject(fsInjectable).readdir,
|
||||
});
|
||||
|
||||
export default readDirectoryInjectable;
|
||||
@ -5,11 +5,16 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type ReadFile = (filePath: string) => Promise<string>;
|
||||
|
||||
const readFileInjectable = getInjectable({
|
||||
id: "read-file",
|
||||
|
||||
instantiate: (di) => (filePath: string) =>
|
||||
di.inject(fsInjectable).readFile(filePath, "utf-8"),
|
||||
instantiate: (di): ReadFile => {
|
||||
const { readFile } = di.inject(fsInjectable);
|
||||
|
||||
return (filePath) => readFile(filePath, "utf-8");
|
||||
},
|
||||
});
|
||||
|
||||
export default readFileInjectable;
|
||||
|
||||
11
src/common/fs/remove-path.global-override-for-injectable.ts
Normal file
11
src/common/fs/remove-path.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import removePathInjectable from "./remove-path.injectable";
|
||||
|
||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
||||
throw new Error("tried to remove a path without override");
|
||||
});
|
||||
@ -5,9 +5,11 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
const readDirInjectable = getInjectable({
|
||||
id: "read-dir",
|
||||
instantiate: (di) => di.inject(fsInjectable).readdir,
|
||||
export type RemovePath = (path: string) => Promise<void>;
|
||||
|
||||
const removePathInjectable = getInjectable({
|
||||
id: "remove-path",
|
||||
instantiate: (di): RemovePath => di.inject(fsInjectable).remove,
|
||||
});
|
||||
|
||||
export default readDirInjectable;
|
||||
export default removePathInjectable;
|
||||
11
src/common/fs/write-file.global-override-for-injectable.ts
Normal file
11
src/common/fs/write-file.global-override-for-injectable.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import writeFileInjectable from "./write-file.injectable";
|
||||
|
||||
export default getGlobalOverride(writeFileInjectable, () => async () => {
|
||||
throw new Error("tried to write file without override");
|
||||
});
|
||||
@ -3,20 +3,28 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import path from "path";
|
||||
import type { WriteFileOptions } from "fs-extra";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type WriteFile = (filePath: string, content: string | Buffer, opts?: WriteFileOptions) => Promise<void>;
|
||||
|
||||
const writeFileInjectable = getInjectable({
|
||||
id: "write-file",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): WriteFile => {
|
||||
const { writeFile, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return async (filePath: string, content: string | Buffer) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
return async (filePath, content, opts) => {
|
||||
await ensureDir(getDirnameOfPath(filePath), {
|
||||
mode: 0o755,
|
||||
...(opts ?? {}),
|
||||
});
|
||||
|
||||
await writeFile(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
...(opts ?? {}),
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
@ -3,37 +3,27 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { EnsureOptions, WriteOptions } from "fs-extra";
|
||||
import path from "path";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
|
||||
import fsInjectable from "./fs.injectable";
|
||||
|
||||
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
|
||||
|
||||
interface Dependencies {
|
||||
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
|
||||
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
|
||||
}
|
||||
const writeJsonFileInjectable = getInjectable({
|
||||
id: "write-json-file",
|
||||
|
||||
const writeJsonFile = ({ writeJson, ensureDir }: Dependencies): WriteJson => async (filePath, content) => {
|
||||
await ensureDir(path.dirname(filePath), { mode: 0o755 });
|
||||
instantiate: (di): WriteJson => {
|
||||
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
|
||||
|
||||
return async (filePath, content) => {
|
||||
await ensureDir(getDirnameOfPath(filePath), { mode: 0o755 });
|
||||
|
||||
await writeJson(filePath, content, {
|
||||
encoding: "utf-8",
|
||||
spaces: 2,
|
||||
});
|
||||
};
|
||||
|
||||
const writeJsonFileInjectable = getInjectable({
|
||||
id: "write-json-file",
|
||||
|
||||
instantiate: (di) => {
|
||||
const { writeJson, ensureDir } = di.inject(fsInjectable);
|
||||
|
||||
return writeJsonFile({
|
||||
writeJson,
|
||||
ensureDir,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -8,9 +8,6 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
|
||||
export const clusterVisibilityHandler = "cluster:visibility";
|
||||
export const clusterRefreshHandler = "cluster:refresh";
|
||||
export const clusterDisconnectHandler = "cluster:disconnect";
|
||||
export const clusterDeleteHandler = "cluster:delete";
|
||||
export const clusterSetDeletingHandler = "cluster:deleting:set";
|
||||
export const clusterClearDeletingHandler = "cluster:deleting:clear";
|
||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
|
||||
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
|
||||
export const clusterStates = "cluster:states";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,38 +3,9 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { JsonApi } from "./json-api";
|
||||
import { apiPrefix, isDebugging, isDevelopment } from "../vars";
|
||||
import { appEventBus } from "../app-event-bus/event-bus";
|
||||
import type { JsonApi } from "./json-api";
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
|
||||
export let apiBase: JsonApi;
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
appEventBus.addListener((event) => {
|
||||
if (event.name !== "lens-proxy" && event.action !== "listen") return;
|
||||
|
||||
const params = event.params as { port?: number };
|
||||
|
||||
if (!params.port) return;
|
||||
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${params.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `localhost:${params.port}`,
|
||||
},
|
||||
export const apiBaseInjectionToken = getInjectionToken<JsonApi>({
|
||||
id: "api-base-token",
|
||||
});
|
||||
});
|
||||
} else {
|
||||
apiBase = new JsonApi({
|
||||
serverAddress: `http://127.0.0.1:${window.location.port}`,
|
||||
apiBase: apiPrefix,
|
||||
debug: isDevelopment || isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": window.location.host,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,11 +4,8 @@
|
||||
*/
|
||||
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import type { KubeJsonApi } from "./kube-json-api";
|
||||
|
||||
export const apiKubeInjectionToken = getInjectionToken<KubeJsonApi>({
|
||||
id: "api-kube-injection-token",
|
||||
});
|
||||
|
||||
export const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken);
|
||||
|
||||
26
src/common/k8s-api/create-json-api.injectable.ts
Normal file
26
src/common/k8s-api/create-json-api.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import fetchInjectable from "../fetch/fetch.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { JsonApiConfig, JsonApiData, JsonApiDependencies, JsonApiParams } from "./json-api";
|
||||
import { JsonApi } from "./json-api";
|
||||
|
||||
export type CreateJsonApi = <Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>>(config: JsonApiConfig, reqInit?: RequestInit) => JsonApi<Data, Params>;
|
||||
|
||||
const createJsonApiInjectable = getInjectable({
|
||||
id: "create-json-api",
|
||||
instantiate: (di): CreateJsonApi => {
|
||||
const deps: JsonApiDependencies = {
|
||||
fetch: di.inject(fetchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (config, reqInit) => new JsonApi(deps, config, reqInit);
|
||||
},
|
||||
});
|
||||
|
||||
export default createJsonApiInjectable;
|
||||
63
src/common/k8s-api/create-kube-api-for-cluster.injectable.ts
Normal file
63
src/common/k8s-api/create-kube-api-for-cluster.injectable.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiKubePrefix } from "../vars";
|
||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||
import { apiBaseInjectionToken } from "./api-base";
|
||||
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
|
||||
import type { KubeApiOptions } from "./kube-api";
|
||||
import { KubeApi } from "./kube-api";
|
||||
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
|
||||
|
||||
export interface CreateKubeApiForLocalClusterConfig {
|
||||
metadata: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateKubeApiForCluster {
|
||||
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
|
||||
cluster: CreateKubeApiForLocalClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api
|
||||
): Api;
|
||||
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
|
||||
cluster: CreateKubeApiForLocalClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>
|
||||
): KubeApi<Object>;
|
||||
}
|
||||
|
||||
const createKubeApiForClusterInjectable = getInjectable({
|
||||
id: "create-kube-api-for-cluster",
|
||||
instantiate: (di): CreateKubeApiForCluster => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
||||
|
||||
return (
|
||||
cluster: CreateKubeApiForLocalClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>,
|
||||
apiClass = KubeApi,
|
||||
) => (
|
||||
new apiClass({
|
||||
objectConstructor: kubeClass,
|
||||
request: createKubeJsonApi(
|
||||
{
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${cluster.metadata.uid}.localhost:${new URL(apiBase.config.serverAddress).port}`,
|
||||
},
|
||||
},
|
||||
),
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeApiForClusterInjectable;
|
||||
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { AgentOptions } from "https";
|
||||
import { Agent } from "https";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
|
||||
import type { KubeApiOptions } from "./kube-api";
|
||||
import { KubeApi } from "./kube-api";
|
||||
import type { KubeJsonApiDataFor, KubeObject, KubeObjectConstructor } from "./kube-object";
|
||||
|
||||
export interface CreateKubeApiForRemoteClusterConfig {
|
||||
cluster: {
|
||||
server: string;
|
||||
caData?: string;
|
||||
skipTLSVerify?: boolean;
|
||||
};
|
||||
user: {
|
||||
token?: string | (() => Promise<string>);
|
||||
clientCertificateData?: string;
|
||||
clientKeyData?: string;
|
||||
};
|
||||
/**
|
||||
* Custom instance of https.agent to use for the requests
|
||||
*
|
||||
* @remarks the custom agent replaced default agent, options skipTLSVerify,
|
||||
* clientCertificateData, clientKeyData and caData are ignored.
|
||||
*/
|
||||
agent?: Agent;
|
||||
}
|
||||
|
||||
export interface CreateKubeApiForRemoteCluster {
|
||||
<Object extends KubeObject, Api extends KubeApi<Object>, Data extends KubeJsonApiDataFor<Object>>(
|
||||
config: CreateKubeApiForRemoteClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass: new (apiOpts: KubeApiOptions<Object>) => Api,
|
||||
): Api;
|
||||
<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>>(
|
||||
config: CreateKubeApiForRemoteClusterConfig,
|
||||
kubeClass: KubeObjectConstructor<Object, Data>,
|
||||
apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>,
|
||||
): KubeApi<Object>;
|
||||
}
|
||||
|
||||
const createKubeApiForRemoteClusterInjectable = getInjectable({
|
||||
id: "create-kube-api-for-remote-cluster",
|
||||
instantiate: (di): CreateKubeApiForRemoteCluster => {
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
||||
|
||||
return (config: CreateKubeApiForRemoteClusterConfig, kubeClass: KubeObjectConstructor<KubeObject, KubeJsonApiDataFor<KubeObject>>, apiClass = KubeApi) => {
|
||||
const reqInit: RequestInit = {};
|
||||
const agentOptions: AgentOptions = {};
|
||||
|
||||
if (config.cluster.skipTLSVerify === true) {
|
||||
agentOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
if (config.user.clientCertificateData) {
|
||||
agentOptions.cert = config.user.clientCertificateData;
|
||||
}
|
||||
|
||||
if (config.user.clientKeyData) {
|
||||
agentOptions.key = config.user.clientKeyData;
|
||||
}
|
||||
|
||||
if (config.cluster.caData) {
|
||||
agentOptions.ca = config.cluster.caData;
|
||||
}
|
||||
|
||||
if (Object.keys(agentOptions).length > 0) {
|
||||
reqInit.agent = new Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (config.agent) {
|
||||
reqInit.agent = config.agent;
|
||||
}
|
||||
|
||||
const token = config.user.token;
|
||||
const request = createKubeJsonApi({
|
||||
serverAddress: config.cluster.server,
|
||||
apiBase: "",
|
||||
debug: isDevelopment,
|
||||
...(token ? {
|
||||
getRequestOptions: async () => ({
|
||||
headers: {
|
||||
"Authorization": `Bearer ${typeof token === "function" ? await token() : token}`,
|
||||
},
|
||||
}),
|
||||
} : {}),
|
||||
}, reqInit);
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass,
|
||||
request,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeApiForRemoteClusterInjectable;
|
||||
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiKubePrefix } from "../vars";
|
||||
import isDebuggingInjectable from "../vars/is-debugging.injectable";
|
||||
import { apiBaseInjectionToken } from "./api-base";
|
||||
import createKubeJsonApiInjectable from "./create-kube-json-api.injectable";
|
||||
import type { KubeJsonApi } from "./kube-json-api";
|
||||
|
||||
export type CreateKubeJsonApiForCluster = (clusterId: string) => KubeJsonApi;
|
||||
|
||||
const createKubeJsonApiForClusterInjectable = getInjectable({
|
||||
id: "create-kube-json-api-for-cluster",
|
||||
instantiate: (di): CreateKubeJsonApiForCluster => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
||||
const isDebugging = di.inject(isDebuggingInjectable);
|
||||
|
||||
return (clusterId) => createKubeJsonApi(
|
||||
{
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDebugging,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Host": `${clusterId}.localhost:${new URL(apiBase.config.serverAddress).port}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeJsonApiForClusterInjectable;
|
||||
26
src/common/k8s-api/create-kube-json-api.injectable.ts
Normal file
26
src/common/k8s-api/create-kube-json-api.injectable.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import fetchInjectable from "../fetch/fetch.injectable";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import type { JsonApiConfig, JsonApiDependencies } from "./json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
|
||||
export type CreateKubeJsonApi = (config: JsonApiConfig, reqInit?: RequestInit) => KubeJsonApi;
|
||||
|
||||
const createKubeJsonApiInjectable = getInjectable({
|
||||
id: "create-kube-json-api",
|
||||
instantiate: (di): CreateKubeJsonApi => {
|
||||
const dependencies: JsonApiDependencies = {
|
||||
fetch: di.inject(fetchInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
};
|
||||
|
||||
return (config, reqInit) => new KubeJsonApi(dependencies, config, reqInit);
|
||||
},
|
||||
});
|
||||
|
||||
export default createKubeJsonApiInjectable;
|
||||
@ -3,8 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { MetricData, IMetricsReqParams } from "./metrics.api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
@ -28,30 +26,6 @@ export class ClusterApi extends KubeApi<Cluster> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsByNodeNames(nodeNames: string[], params?: IMetricsReqParams): Promise<ClusterMetricData> {
|
||||
const nodes = nodeNames.join("|");
|
||||
const opts = { category: "cluster", nodes };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
memoryUsage: opts,
|
||||
workloadMemoryUsage: opts,
|
||||
memoryRequests: opts,
|
||||
memoryLimits: opts,
|
||||
memoryCapacity: opts,
|
||||
memoryAllocatableCapacity: opts,
|
||||
cpuUsage: opts,
|
||||
cpuRequests: opts,
|
||||
cpuLimits: opts,
|
||||
cpuCapacity: opts,
|
||||
cpuAllocatableCapacity: opts,
|
||||
podUsage: opts,
|
||||
podCapacity: opts,
|
||||
podAllocatableCapacity: opts,
|
||||
fsSize: opts,
|
||||
fsUsage: opts,
|
||||
}, params);
|
||||
}
|
||||
|
||||
export enum ClusterStatus {
|
||||
ACTIVE = "Active",
|
||||
CREATING = "Creating",
|
||||
@ -59,21 +33,6 @@ export enum ClusterStatus {
|
||||
ERROR = "Error",
|
||||
}
|
||||
|
||||
export interface ClusterMetricData extends Partial<Record<string, MetricData>> {
|
||||
memoryUsage: MetricData;
|
||||
memoryRequests: MetricData;
|
||||
memoryLimits: MetricData;
|
||||
memoryCapacity: MetricData;
|
||||
cpuUsage: MetricData;
|
||||
cpuRequests: MetricData;
|
||||
cpuLimits: MetricData;
|
||||
cpuCapacity: MetricData;
|
||||
podUsage: MetricData;
|
||||
podCapacity: MetricData;
|
||||
fsSize: MetricData;
|
||||
fsUsage: MetricData;
|
||||
}
|
||||
|
||||
export interface Cluster {
|
||||
spec: {
|
||||
clusterNetwork?: {
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData } from "./pod.api";
|
||||
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodTemplateSpec } from "./types/pod-template-spec";
|
||||
@ -90,20 +88,3 @@ export class DaemonSetApi extends KubeApi<DaemonSet> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForDaemonSets(daemonsets: DaemonSet[], namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const podSelector = daemonsets.map(daemonset => `${daemonset.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,8 +7,7 @@ import moment from "moment";
|
||||
|
||||
import type { DerivedKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData, PodSpec } from "./pod.api";
|
||||
import type { PodSpec } from "./pod.api";
|
||||
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { hasTypedProperty, isNumber, isObject } from "../../utils";
|
||||
@ -70,23 +69,6 @@ export class DeploymentApi extends KubeApi<Deployment> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface DeploymentSpec {
|
||||
replicas: number;
|
||||
selector: LabelSelector;
|
||||
|
||||
@ -3,72 +3,9 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { compile } from "path-to-regexp";
|
||||
import { apiBase } from "../index";
|
||||
import { stringify } from "querystring";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import { autoBind, bifurcateArray, isDefined } from "../../utils";
|
||||
import { autoBind, bifurcateArray } from "../../utils";
|
||||
import Joi from "joi";
|
||||
|
||||
export type RepoHelmChartList = Record<string, RawHelmChart[]>;
|
||||
|
||||
export interface IHelmChartDetails {
|
||||
readme: string;
|
||||
versions: HelmChart[];
|
||||
}
|
||||
|
||||
const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
|
||||
repo?: string;
|
||||
name?: string;
|
||||
}) => string;
|
||||
|
||||
/**
|
||||
* Get a list of all helm charts from all saved helm repos
|
||||
*/
|
||||
export async function listCharts(): Promise<HelmChart[]> {
|
||||
const data = await apiBase.get<Record<string, RepoHelmChartList>>(endpoint());
|
||||
|
||||
return Object
|
||||
.values(data)
|
||||
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array<RawHelmChart[]>())
|
||||
.map(([chart]) => HelmChart.create(chart, { onError: "log" }))
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
export interface GetChartDetailsOptions {
|
||||
version?: string;
|
||||
reqInit?: RequestInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the readme and all versions of a chart
|
||||
* @param repo The repo to get from
|
||||
* @param name The name of the chart to request the data of
|
||||
* @param options.version The version of the chart's readme to get, default latest
|
||||
* @param options.reqInit A way for passing in an abort controller or other browser request options
|
||||
*/
|
||||
export async function getChartDetails(repo: string, name: string, { version, reqInit }: GetChartDetailsOptions = {}): Promise<IHelmChartDetails> {
|
||||
const path = endpoint({ repo, name });
|
||||
|
||||
const { readme, ...data } = await apiBase.get<IHelmChartDetails>(`${path}?${stringify({ version })}`, undefined, reqInit);
|
||||
const versions = data.versions.map(version => HelmChart.create(version, { onError: "log" })).filter(isDefined);
|
||||
|
||||
return {
|
||||
readme,
|
||||
versions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get chart values related to a specific repos' version of a chart
|
||||
* @param repo The repo to get from
|
||||
* @param name The name of the chart to request the data of
|
||||
* @param version The version to get the values from
|
||||
*/
|
||||
export async function getChartValues(repo: string, name: string, version: string): Promise<string> {
|
||||
return apiBase.get<string>(`/v2/charts/${repo}/${name}/values?${stringify({ version })}`);
|
||||
}
|
||||
|
||||
export interface RawHelmChart {
|
||||
apiVersion: string;
|
||||
name: string;
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import type { RawHelmChart } from "../helm-charts.api";
|
||||
import { HelmChart } from "../helm-charts.api";
|
||||
import { isDefined } from "../../../utils";
|
||||
|
||||
export type RequestHelmCharts = () => Promise<HelmChart[]>;
|
||||
export type RepoHelmChartList = Record<string, RawHelmChart[]>;
|
||||
|
||||
/**
|
||||
* Get a list of all helm charts from all saved helm repos
|
||||
*/
|
||||
const requestHelmChartsInjectable = getInjectable({
|
||||
id: "request-helm-charts",
|
||||
instantiate: (di) => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return async () => {
|
||||
const data = await apiBase.get<Record<string, RepoHelmChartList>>("/v2/charts");
|
||||
|
||||
return Object
|
||||
.values(data)
|
||||
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array<RawHelmChart[]>())
|
||||
.map(([chart]) => HelmChart.create(chart, { onError: "log" }))
|
||||
.filter(isDefined);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmChartsInjectable;
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
const requestReadmeEndpoint = urlBuilderFor("/v2/charts/:repo/:name/readme");
|
||||
|
||||
export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => Promise<string>;
|
||||
|
||||
const requestHelmChartReadmeInjectable = getInjectable({
|
||||
id: "request-helm-chart-readme",
|
||||
instantiate: (di): RequestHelmChartReadme => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (repo, name, version) => (
|
||||
apiBase.get(requestReadmeEndpoint.compile({ name, repo }, { version }))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmChartReadmeInjectable;
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
const requestValuesEndpoint = urlBuilderFor("/v2/charts/:repo/:name/values");
|
||||
|
||||
export type RequestHelmChartValues = (repo: string, name: string, version: string) => Promise<string>;
|
||||
|
||||
const requestHelmChartValuesInjectable = getInjectable({
|
||||
id: "request-helm-chart-values",
|
||||
instantiate: (di): RequestHelmChartValues => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (repo, name, version) => (
|
||||
apiBase.get(requestValuesEndpoint.compile({ repo, name }, { version }))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmChartValuesInjectable;
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import { HelmChart } from "../helm-charts.api";
|
||||
import type { RawHelmChart } from "../helm-charts.api";
|
||||
import { isDefined } from "../../../utils";
|
||||
|
||||
const requestVersionsEndpoint = urlBuilderFor("/v2/charts/:repo/:name/versions");
|
||||
|
||||
export type RequestHelmChartVersions = (repo: string, chartName: string) => Promise<HelmChart[]>;
|
||||
|
||||
const requestHelmChartVersionsInjectable = getInjectable({
|
||||
id: "request-helm-chart-versions",
|
||||
instantiate: (di): RequestHelmChartVersions => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return async (repo, name) => {
|
||||
const rawVersions = await apiBase.get(requestVersionsEndpoint.compile({ name, repo })) as RawHelmChart[];
|
||||
|
||||
return rawVersions
|
||||
.map(version => HelmChart.create(version, { onError: "log" }))
|
||||
.filter(isDefined);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmChartVersionsInjectable;
|
||||
@ -3,65 +3,14 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { apiBase } from "../index";
|
||||
import type { ItemObject } from "../../item.store";
|
||||
import type { JsonApiData } from "../json-api";
|
||||
import { buildURLPositional } from "../../utils/buildUrl";
|
||||
import type { HelmReleaseDetails } from "../../../renderer/components/+helm-releases/release-details/release-details-model/call-for-helm-release/call-for-helm-release-details/call-for-helm-release-details.injectable";
|
||||
import type { HelmReleaseDetails } from "./helm-releases.api/request-details.injectable";
|
||||
|
||||
export interface HelmReleaseUpdateDetails {
|
||||
log: string;
|
||||
release: HelmReleaseDetails;
|
||||
}
|
||||
|
||||
export interface HelmReleaseRevision {
|
||||
revision: number;
|
||||
updated: string;
|
||||
status: string;
|
||||
chart: string;
|
||||
app_version: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
type EndpointParams = {}
|
||||
| { namespace: string }
|
||||
| { namespace: string; name: string }
|
||||
| { namespace: string; name: string; route: string };
|
||||
|
||||
interface EndpointQuery {
|
||||
all?: boolean;
|
||||
}
|
||||
|
||||
export const endpoint = buildURLPositional<EndpointParams, EndpointQuery>("/v2/releases/:namespace?/:name?/:route?");
|
||||
|
||||
export async function deleteRelease(name: string, namespace: string): Promise<JsonApiData> {
|
||||
const path = endpoint({ name, namespace });
|
||||
|
||||
return apiBase.del(path);
|
||||
}
|
||||
|
||||
export async function getReleaseValues(name: string, namespace: string, all?: boolean): Promise<string> {
|
||||
const route = "values";
|
||||
const path = endpoint({ name, namespace, route }, { all });
|
||||
|
||||
return apiBase.get<string>(path);
|
||||
}
|
||||
|
||||
export async function getReleaseHistory(name: string, namespace: string): Promise<HelmReleaseRevision[]> {
|
||||
const route = "history";
|
||||
const path = endpoint({ name, namespace, route });
|
||||
|
||||
return apiBase.get(path);
|
||||
}
|
||||
|
||||
export async function rollbackRelease(name: string, namespace: string, revision: number): Promise<JsonApiData> {
|
||||
const route = "rollback";
|
||||
const path = endpoint({ name, namespace, route });
|
||||
const data = { revision };
|
||||
|
||||
return apiBase.put(path, { data });
|
||||
}
|
||||
|
||||
export interface HelmReleaseDto {
|
||||
appVersion: string;
|
||||
name: string;
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../../../test-utils/get-global-override";
|
||||
import requestHelmReleaseConfigurationInjectable from "./request-configuration.injectable";
|
||||
|
||||
export default getGlobalOverride(requestHelmReleaseConfigurationInjectable, () => () => {
|
||||
throw new Error("Tried to call requestHelmReleaseConfiguration with no override");
|
||||
});
|
||||
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export type RequestHelmReleaseConfiguration = (
|
||||
name: string,
|
||||
namespace: string,
|
||||
all: boolean
|
||||
) => Promise<string>;
|
||||
|
||||
const requestConfigurationEnpoint = urlBuilderFor("/v2/releases/:namespace/:name/values");
|
||||
|
||||
const requestHelmReleaseConfigurationInjectable = getInjectable({
|
||||
id: "request-helm-release-configuration",
|
||||
|
||||
instantiate: (di): RequestHelmReleaseConfiguration => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, namespace, all: boolean) => (
|
||||
apiBase.get(requestConfigurationEnpoint.compile({ name, namespace }, { all }))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseConfigurationInjectable;
|
||||
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import yaml from "js-yaml";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { HelmReleaseUpdateDetails } from "../helm-releases.api";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
|
||||
interface HelmReleaseCreatePayload {
|
||||
name?: string;
|
||||
repo: string;
|
||||
chart: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
values: string;
|
||||
}
|
||||
|
||||
export type RequestCreateHelmRelease = (payload: HelmReleaseCreatePayload) => Promise<HelmReleaseUpdateDetails>;
|
||||
|
||||
const requestCreateEndpoint = urlBuilderFor("/v2/releases");
|
||||
|
||||
const requestCreateHelmReleaseInjectable = getInjectable({
|
||||
id: "request-create-helm-release",
|
||||
|
||||
instantiate: (di): RequestCreateHelmRelease => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return ({ repo, chart, values, ...data }) => {
|
||||
return apiBase.post(requestCreateEndpoint.compile({}), {
|
||||
data: {
|
||||
chart: `${repo}/${chart}`,
|
||||
values: yaml.load(values),
|
||||
...data,
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestCreateHelmReleaseInjectable;
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export type RequestDeleteHelmRelease = (name: string, namespace: string) => Promise<void>;
|
||||
|
||||
const requestDeleteEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
||||
|
||||
const requestDeleteHelmReleaseInjectable = getInjectable({
|
||||
id: "request-delete-helm-release",
|
||||
instantiate: (di): RequestDeleteHelmRelease => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, namespace) => apiBase.del(requestDeleteEndpoint.compile({ name, namespace }));
|
||||
},
|
||||
});
|
||||
|
||||
export default requestDeleteHelmReleaseInjectable;
|
||||
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { KubeJsonApiData } from "../../kube-json-api";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
|
||||
export interface HelmReleaseDetails {
|
||||
resources: KubeJsonApiData[];
|
||||
name: string;
|
||||
namespace: string;
|
||||
version: string;
|
||||
config: string; // release values
|
||||
manifest: string;
|
||||
info: {
|
||||
deleted: string;
|
||||
description: string;
|
||||
first_deployed: string;
|
||||
last_deployed: string;
|
||||
notes: string;
|
||||
status: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type CallForHelmReleaseDetails = (name: string, namespace: string) => Promise<HelmReleaseDetails>;
|
||||
|
||||
const requestDetailsEnpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
||||
|
||||
const requestHelmReleaseDetailsInjectable = getInjectable({
|
||||
id: "call-for-helm-release-details",
|
||||
|
||||
instantiate: (di): CallForHelmReleaseDetails => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, namespace) => apiBase.get(requestDetailsEnpoint.compile({ name, namespace }));
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseDetailsInjectable;
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export interface HelmReleaseRevision {
|
||||
revision: number;
|
||||
updated: string;
|
||||
status: string;
|
||||
chart: string;
|
||||
app_version: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type RequestHelmReleaseHistory = (name: string, namespace: string) => Promise<HelmReleaseRevision[]>;
|
||||
|
||||
const requestHistoryEnpoint = urlBuilderFor("/v2/releases/:namespace/:name/history");
|
||||
|
||||
const requestHelmReleaseHistoryInjectable = getInjectable({
|
||||
id: "request-helm-release-history",
|
||||
instantiate: (di): RequestHelmReleaseHistory => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, namespace) => apiBase.get(requestHistoryEnpoint.compile({ name, namespace }));
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseHistoryInjectable;
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import type { HelmReleaseDto } from "../helm-releases.api";
|
||||
|
||||
export type RequestHelmReleases = (namespace?: string) => Promise<HelmReleaseDto[]>;
|
||||
|
||||
const requestHelmReleasesEndpoint = urlBuilderFor("/v2/releases/:namespace?");
|
||||
|
||||
const requestHelmReleasesInjectable = getInjectable({
|
||||
id: "request-helm-releases",
|
||||
|
||||
instantiate: (di): RequestHelmReleases => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (namespace) => apiBase.get(requestHelmReleasesEndpoint.compile({ namespace }));
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleasesInjectable;
|
||||
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export type RequestHelmReleaseRollback = (name: string, namespace: string, revision: number) => Promise<void>;
|
||||
|
||||
const requestRollbackEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
||||
|
||||
const requestHelmReleaseRollbackInjectable = getInjectable({
|
||||
id: "request-helm-release-rollback",
|
||||
instantiate: (di): RequestHelmReleaseRollback => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return async (name, namespace, revision) => {
|
||||
await apiBase.put(
|
||||
requestRollbackEndpoint.compile({ name, namespace }),
|
||||
{ data: { revision }},
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseRollbackInjectable;
|
||||
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import yaml from "js-yaml";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
|
||||
interface HelmReleaseUpdatePayload {
|
||||
repo: string;
|
||||
chart: string;
|
||||
version: string;
|
||||
values: string;
|
||||
}
|
||||
|
||||
export type RequestHelmReleaseUpdate = (
|
||||
name: string,
|
||||
namespace: string,
|
||||
payload: HelmReleaseUpdatePayload
|
||||
) => Promise<{ updateWasSuccessful: true } | { updateWasSuccessful: false; error: unknown }>;
|
||||
|
||||
const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");
|
||||
|
||||
const requestHelmReleaseUpdateInjectable = getInjectable({
|
||||
id: "request-helm-release-update",
|
||||
|
||||
instantiate: (di): RequestHelmReleaseUpdate => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return async (name, namespace, { repo, chart, values, ...data }) => {
|
||||
try {
|
||||
await apiBase.put(requestUpdateEndpoint.compile({ name, namespace }), {
|
||||
data: {
|
||||
chart: `${repo}/${chart}`,
|
||||
values: yaml.load(values),
|
||||
...data,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
return { updateWasSuccessful: false, error: e };
|
||||
}
|
||||
|
||||
return { updateWasSuccessful: true };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseUpdateInjectable;
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { urlBuilderFor } from "../../../utils/buildUrl";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export type RequestHelmReleaseValues = (name: string, namespace: string, all?: boolean) => Promise<string>;
|
||||
|
||||
const requestValuesEndpoint = urlBuilderFor("/v2/release/:namespace/:name/values");
|
||||
|
||||
const requestHelmReleaseValuesInjectable = getInjectable({
|
||||
id: "request-helm-release-values",
|
||||
instantiate: (di): RequestHelmReleaseValues => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, namespace, all) => apiBase.get(requestValuesEndpoint.compile({ name, namespace }, { all }));
|
||||
},
|
||||
});
|
||||
|
||||
export default requestHelmReleaseValuesInjectable;
|
||||
@ -6,8 +6,6 @@
|
||||
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { hasTypedProperty, isString, iter } from "../../utils";
|
||||
import type { MetricData } from "./metrics.api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
@ -24,26 +22,6 @@ export class IngressApi extends KubeApi<Ingress> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForIngress(ingress: string, namespace: string): Promise<IngressMetricData> {
|
||||
const opts = { category: "ingress", ingress, namespace };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
bytesSentSuccess: opts,
|
||||
bytesSentFailure: opts,
|
||||
requestDurationSeconds: opts,
|
||||
responseDurationSeconds: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface IngressMetricData extends Partial<Record<string, MetricData>> {
|
||||
bytesSentSuccess: MetricData;
|
||||
bytesSentFailure: MetricData;
|
||||
requestDurationSeconds: MetricData;
|
||||
responseDurationSeconds: MetricData;
|
||||
}
|
||||
|
||||
export interface ILoadBalancerIngress {
|
||||
hostname?: string;
|
||||
ip?: string;
|
||||
|
||||
@ -5,8 +5,7 @@
|
||||
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData, PodSpec } from "./pod.api";
|
||||
import type { PodSpec } from "./pod.api";
|
||||
import type { Container } from "./types/container";
|
||||
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
@ -103,20 +102,3 @@ export class JobApi extends KubeApi<Job> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
// Metrics api
|
||||
|
||||
import moment from "moment";
|
||||
import { apiBase } from "../index";
|
||||
import { isDefined, object } from "../../utils";
|
||||
|
||||
export interface MetricData {
|
||||
status: string;
|
||||
@ -29,63 +29,6 @@ export interface MetricResult {
|
||||
values: [number, string][];
|
||||
}
|
||||
|
||||
export interface MetricProviderInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
isConfigurable: boolean;
|
||||
}
|
||||
|
||||
export interface IMetricsReqParams {
|
||||
start?: number | string; // timestamp in seconds or valid date-string
|
||||
end?: number | string;
|
||||
step?: number; // step in seconds (default: 60s = each point 1m)
|
||||
range?: number; // time-range in seconds for data aggregation (default: 3600s = last 1h)
|
||||
namespace?: string; // rbac-proxy validation param
|
||||
}
|
||||
|
||||
export interface IResourceMetrics<T extends MetricData> {
|
||||
[metric: string]: T;
|
||||
cpuUsage: T;
|
||||
memoryUsage: T;
|
||||
fsUsage: T;
|
||||
fsWrites: T;
|
||||
fsReads: T;
|
||||
networkReceive: T;
|
||||
networkTransmit: T;
|
||||
}
|
||||
|
||||
async function getMetrics(query: string, reqParams?: IMetricsReqParams): Promise<MetricData>;
|
||||
async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise<MetricData[]>;
|
||||
async function getMetrics<MetricNames extends string>(query: Record<MetricNames, Partial<Record<string, string>>>, reqParams?: IMetricsReqParams): Promise<Record<MetricNames, MetricData>>;
|
||||
|
||||
async function getMetrics(query: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, reqParams: IMetricsReqParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
|
||||
const { range = 3600, step = 60, namespace } = reqParams;
|
||||
let { start, end } = reqParams;
|
||||
|
||||
if (!start && !end) {
|
||||
const timeNow = Date.now() / 1000;
|
||||
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
|
||||
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
|
||||
return apiBase.post("/metrics", {
|
||||
data: query,
|
||||
query: {
|
||||
start, end, step,
|
||||
"kubernetes_namespace": namespace,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const metricsApi = {
|
||||
getMetrics,
|
||||
async getMetricProviders(): Promise<MetricProviderInfo[]> {
|
||||
return apiBase.get("/metrics/providers");
|
||||
},
|
||||
};
|
||||
|
||||
export function normalizeMetrics(metrics: MetricData | undefined | null, frames = 60): MetricData {
|
||||
if (!metrics?.data?.result) {
|
||||
return {
|
||||
@ -145,7 +88,7 @@ export function isMetricsEmpty(metrics: Partial<Record<string, MetricData>>) {
|
||||
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
|
||||
}
|
||||
|
||||
export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | null | undefined, itemName: string): Partial<Record<string, MetricData>> | undefined {
|
||||
export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys, MetricData>> | null | undefined, itemName: string): Partial<Record<Keys, MetricData>> | undefined {
|
||||
if (!metrics) {
|
||||
return undefined;
|
||||
}
|
||||
@ -166,22 +109,16 @@ export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | nu
|
||||
return itemMetrics;
|
||||
}
|
||||
|
||||
export function getMetricLastPoints<T extends Partial<Record<string, MetricData>>>(metrics: T): Record<keyof T, number> {
|
||||
const result: Partial<{ [metric: string]: number }> = {};
|
||||
|
||||
Object.keys(metrics).forEach(metricName => {
|
||||
export function getMetricLastPoints<Keys extends string>(metrics: Partial<Record<Keys, MetricData>>): Partial<Record<Keys, number>> {
|
||||
return object.fromEntries(
|
||||
object.entries(metrics)
|
||||
.map(([metricName, metric]) => {
|
||||
try {
|
||||
const metric = metrics[metricName];
|
||||
|
||||
if (metric?.data.result.length) {
|
||||
result[metricName] = +metric.data.result[0].values.slice(-1)[0][1];
|
||||
}
|
||||
return [metricName, +metric.data.result[0].values.slice(-1)[0][1]] as const;
|
||||
} catch {
|
||||
// ignore error
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
return result as Record<keyof T, number>;
|
||||
})
|
||||
.filter(isDefined),
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import type { RequestMetricsParams } from "./request-metrics.injectable";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface ClusterMetricData {
|
||||
memoryUsage: MetricData;
|
||||
memoryRequests: MetricData;
|
||||
memoryLimits: MetricData;
|
||||
memoryCapacity: MetricData;
|
||||
memoryAllocatableCapacity: MetricData;
|
||||
cpuUsage: MetricData;
|
||||
cpuRequests: MetricData;
|
||||
cpuLimits: MetricData;
|
||||
cpuCapacity: MetricData;
|
||||
cpuAllocatableCapacity: MetricData;
|
||||
podUsage: MetricData;
|
||||
podCapacity: MetricData;
|
||||
podAllocatableCapacity: MetricData;
|
||||
fsSize: MetricData;
|
||||
fsUsage: MetricData;
|
||||
}
|
||||
|
||||
export type RequestClusterMetricsByNodeNames = (nodeNames: string[], params?: RequestMetricsParams) => Promise<ClusterMetricData>;
|
||||
|
||||
const requestClusterMetricsByNodeNamesInjectable = getInjectable({
|
||||
id: "get-cluster-metrics-by-node-names",
|
||||
instantiate: (di): RequestClusterMetricsByNodeNames => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (nodeNames, params) => {
|
||||
const opts = {
|
||||
category: "cluster",
|
||||
nodes: nodeNames.join("|"),
|
||||
};
|
||||
|
||||
return requestMetrics({
|
||||
memoryUsage: opts,
|
||||
workloadMemoryUsage: opts,
|
||||
memoryRequests: opts,
|
||||
memoryLimits: opts,
|
||||
memoryCapacity: opts,
|
||||
memoryAllocatableCapacity: opts,
|
||||
cpuUsage: opts,
|
||||
cpuRequests: opts,
|
||||
cpuLimits: opts,
|
||||
cpuCapacity: opts,
|
||||
cpuAllocatableCapacity: opts,
|
||||
podUsage: opts,
|
||||
podCapacity: opts,
|
||||
podAllocatableCapacity: opts,
|
||||
fsSize: opts,
|
||||
fsUsage: opts,
|
||||
}, params);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestClusterMetricsByNodeNamesInjectable;
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface IngressMetricData {
|
||||
bytesSentSuccess: MetricData;
|
||||
bytesSentFailure: MetricData;
|
||||
requestDurationSeconds: MetricData;
|
||||
responseDurationSeconds: MetricData;
|
||||
}
|
||||
|
||||
export type RequestIngressMetrics = (ingress: string, namespace: string) => Promise<IngressMetricData>;
|
||||
|
||||
const requestIngressMetricsInjectable = getInjectable({
|
||||
id: "request-ingress-metrics",
|
||||
instantiate: (di): RequestIngressMetrics => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (ingress, namespace) => {
|
||||
const opts = { category: "ingress", ingress, namespace };
|
||||
|
||||
return requestMetrics({
|
||||
bytesSentSuccess: opts,
|
||||
bytesSentFailure: opts,
|
||||
requestDurationSeconds: opts,
|
||||
responseDurationSeconds: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestIngressMetricsInjectable;
|
||||
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface NodeMetricData {
|
||||
memoryUsage: MetricData;
|
||||
workloadMemoryUsage: MetricData;
|
||||
memoryCapacity: MetricData;
|
||||
memoryAllocatableCapacity: MetricData;
|
||||
cpuUsage: MetricData;
|
||||
cpuCapacity: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsSize: MetricData;
|
||||
}
|
||||
|
||||
export type RequestAllNodeMetrics = () => Promise<NodeMetricData>;
|
||||
|
||||
const requestAllNodeMetricsInjectable = getInjectable({
|
||||
id: "request-all-node-metrics",
|
||||
instantiate: (di): RequestAllNodeMetrics => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return () => {
|
||||
const opts = { category: "nodes" };
|
||||
|
||||
return requestMetrics({
|
||||
memoryUsage: opts,
|
||||
workloadMemoryUsage: opts,
|
||||
memoryCapacity: opts,
|
||||
memoryAllocatableCapacity: opts,
|
||||
cpuUsage: opts,
|
||||
cpuCapacity: opts,
|
||||
fsSize: opts,
|
||||
fsUsage: opts,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestAllNodeMetricsInjectable;
|
||||
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { getSecondsFromUnixEpoch } from "../../../utils/date/get-current-date-time";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
|
||||
|
||||
export interface RequestMetricsParams {
|
||||
/**
|
||||
* timestamp in seconds or valid date-string
|
||||
*/
|
||||
start?: number | string;
|
||||
|
||||
/**
|
||||
* timestamp in seconds or valid date-string
|
||||
*/
|
||||
end?: number | string;
|
||||
|
||||
/**
|
||||
* step in seconds
|
||||
* @default 60 (1 minute)
|
||||
*/
|
||||
step?: number;
|
||||
|
||||
/**
|
||||
* time-range in seconds for data aggregation
|
||||
* @default 3600 (1 hour)
|
||||
*/
|
||||
range?: number;
|
||||
|
||||
/**
|
||||
* rbac-proxy validation param
|
||||
*/
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface RequestMetrics {
|
||||
(query: string, params?: RequestMetricsParams): Promise<MetricData>;
|
||||
(query: string[], params?: RequestMetricsParams): Promise<MetricData[]>;
|
||||
<Keys extends string>(query: Record<Keys, Partial<Record<string, string>>>, params?: RequestMetricsParams): Promise<Record<Keys, MetricData>>;
|
||||
}
|
||||
|
||||
const requestMetricsInjectable = getInjectable({
|
||||
id: "request-metrics",
|
||||
instantiate: (di) => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (async (query: object, params: RequestMetricsParams = {}) => {
|
||||
const { range = 3600, step = 60, namespace } = params;
|
||||
let { start, end } = params;
|
||||
|
||||
if (!start && !end) {
|
||||
const now = getSecondsFromUnixEpoch();
|
||||
|
||||
start = now - range;
|
||||
end = now;
|
||||
}
|
||||
|
||||
return apiBase.post("/metrics", {
|
||||
data: query,
|
||||
query: {
|
||||
start, end, step,
|
||||
"kubernetes_namespace": namespace,
|
||||
},
|
||||
});
|
||||
}) as RequestMetrics;
|
||||
},
|
||||
});
|
||||
|
||||
export default requestMetricsInjectable;
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import type { PersistentVolumeClaim } from "../persistent-volume-claim.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface PersistentVolumeClaimMetricData {
|
||||
diskUsage: MetricData;
|
||||
diskCapacity: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPersistentVolumeClaimMetrics = (claim: PersistentVolumeClaim) => Promise<PersistentVolumeClaimMetricData>;
|
||||
|
||||
const requestPersistentVolumeClaimMetricsInjectable = getInjectable({
|
||||
id: "request-persistent-volume-claim-metrics",
|
||||
instantiate: (di): RequestPersistentVolumeClaimMetrics => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (claim) => {
|
||||
const opts = { category: "pvc", pvc: claim.getName(), namespace: claim.getNs() };
|
||||
|
||||
return requestMetrics({
|
||||
diskUsage: opts,
|
||||
diskCapacity: opts,
|
||||
}, {
|
||||
namespace: opts.namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPersistentVolumeClaimMetricsInjectable;
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { DaemonSet } from "../daemon-set.api";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface DaemonSetPodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsForDaemonSets = (daemonsets: DaemonSet[], namespace: string, selector?: string) => Promise<DaemonSetPodMetricData>;
|
||||
|
||||
const requestPodMetricsForDaemonSetsInjectable = getInjectable({
|
||||
id: "request-pod-metrics-for-daemon-sets",
|
||||
instantiate: (di): RequestPodMetricsForDaemonSets => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (daemonSets, namespace, selector = "") => {
|
||||
const podSelector = daemonSets.map(daemonSet => `${daemonSet.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsForDaemonSetsInjectable;
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Deployment } from "../deployment.api";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface DeploymentPodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsForDeployments = (deployments: Deployment[], namespace: string, selector?: string) => Promise<DeploymentPodMetricData>;
|
||||
|
||||
const requestPodMetricsForDeploymentsInjectable = getInjectable({
|
||||
id: "request-pod-metrics-for-deployments",
|
||||
instantiate: (di): RequestPodMetricsForDeployments => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (deployments, namespace, selector = "") => {
|
||||
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsForDeploymentsInjectable;
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Job } from "../job.api";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface JobPodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsForJobs = (jobs: Job[], namespace: string, selector?: string) => Promise<JobPodMetricData>;
|
||||
|
||||
const requestPodMetricsForJobsInjectable = getInjectable({
|
||||
id: "request-pod-metrics-for-jobs",
|
||||
instantiate: (di): RequestPodMetricsForJobs => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (jobs, namespace, selector) => {
|
||||
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsForJobsInjectable;
|
||||
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import type { ReplicaSet } from "../replica-set.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface ReplicaSetPodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsForReplicaSets = (replicaSets: ReplicaSet[], namespace: string, selector?: string) => Promise<ReplicaSetPodMetricData>;
|
||||
|
||||
const requestPodMetricsForReplicaSetsInjectable = getInjectable({
|
||||
id: "request-pod-metrics-for-replica-sets",
|
||||
instantiate: (di): RequestPodMetricsForReplicaSets => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (replicaSets, namespace, selector = "") => {
|
||||
const podSelector = replicaSets.map(replicaSet => `${replicaSet.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsForReplicaSetsInjectable;
|
||||
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import type { StatefulSet } from "../stateful-set.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface StatefulSetPodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsForStatefulSets = (statefulSets: StatefulSet[], namespace: string, selector?: string) => Promise<StatefulSetPodMetricData>;
|
||||
|
||||
const requestPodMetricsForStatefulSetsInjectable = getInjectable({
|
||||
id: "request-pod-metrics-for-stateful-sets",
|
||||
instantiate: (di): RequestPodMetricsForStatefulSets => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (statefulSets, namespace, selector = "") => {
|
||||
const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsForStatefulSetsInjectable;
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface PodMetricInNamespaceData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetricsInNamespace = (namespace: string, selector?: string) => Promise<PodMetricInNamespaceData>;
|
||||
|
||||
const requestPodMetricsInNamespaceInjectable = getInjectable({
|
||||
id: "request-pod-metrics-in-namespace",
|
||||
instantiate: (di): RequestPodMetricsInNamespace => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (namespace, selector) => {
|
||||
const opts = { category: "pods", pods: ".*", namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsInNamespaceInjectable;
|
||||
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { MetricData } from "../metrics.api";
|
||||
import type { Pod } from "../pod.api";
|
||||
import requestMetricsInjectable from "./request-metrics.injectable";
|
||||
|
||||
export interface PodMetricData {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
cpuRequests: MetricData;
|
||||
cpuLimits: MetricData;
|
||||
memoryRequests: MetricData;
|
||||
memoryLimits: MetricData;
|
||||
}
|
||||
|
||||
export type RequestPodMetrics = (pods: Pod[], namespace: string, selector?: string) => Promise<PodMetricData>;
|
||||
|
||||
const requestPodMetricsInjectable = getInjectable({
|
||||
id: "request-pod-metrics",
|
||||
instantiate: (di): RequestPodMetrics => {
|
||||
const requestMetrics = di.inject(requestMetricsInjectable);
|
||||
|
||||
return (pods, namespace, selector = "pod, namespace") => {
|
||||
const podSelector = pods.map(pod => pod.getName()).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return requestMetrics({
|
||||
cpuUsage: opts,
|
||||
cpuRequests: opts,
|
||||
cpuLimits: opts,
|
||||
memoryUsage: opts,
|
||||
memoryRequests: opts,
|
||||
memoryLimits: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default requestPodMetricsInjectable;
|
||||
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
|
||||
export interface MetricProviderInfo {
|
||||
name: string;
|
||||
id: string;
|
||||
isConfigurable: boolean;
|
||||
}
|
||||
|
||||
export type RequestMetricsProviders = () => Promise<MetricProviderInfo[]>;
|
||||
|
||||
const requestMetricsProvidersInjectable = getInjectable({
|
||||
id: "request-metrics-providers",
|
||||
instantiate: (di): RequestMetricsProviders => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return () => apiBase.get("/metrics/providers");
|
||||
},
|
||||
});
|
||||
|
||||
export default requestMetricsProvidersInjectable;
|
||||
@ -7,8 +7,6 @@ import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { ClusterScopedMetadata, KubeObjectStatus } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData } from "./pod.api";
|
||||
|
||||
export enum NamespaceStatusKind {
|
||||
ACTIVE = "Active",
|
||||
@ -45,19 +43,3 @@ export class NamespaceApi extends KubeApi<Namespace> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForNamespace(namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const opts = { category: "pods", pods: ".*", namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@
|
||||
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils";
|
||||
import type { MetricData } from "./metrics.api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { TypedRegEx } from "typed-regex";
|
||||
@ -21,32 +19,6 @@ export class NodeApi extends KubeApi<Node> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForAllNodes(): Promise<NodeMetricData> {
|
||||
const opts = { category: "nodes" };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
memoryUsage: opts,
|
||||
workloadMemoryUsage: opts,
|
||||
memoryCapacity: opts,
|
||||
memoryAllocatableCapacity: opts,
|
||||
cpuUsage: opts,
|
||||
cpuCapacity: opts,
|
||||
fsSize: opts,
|
||||
fsUsage: opts,
|
||||
});
|
||||
}
|
||||
|
||||
export interface NodeMetricData extends Partial<Record<string, MetricData>> {
|
||||
memoryUsage: MetricData;
|
||||
workloadMemoryUsage: MetricData;
|
||||
memoryCapacity: MetricData;
|
||||
memoryAllocatableCapacity: MetricData;
|
||||
cpuUsage: MetricData;
|
||||
cpuCapacity: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsSize: MetricData;
|
||||
}
|
||||
|
||||
export interface NodeTaint {
|
||||
key: string;
|
||||
value?: string;
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
|
||||
import type { LabelSelector, NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { MetricData } from "./metrics.api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { Pod } from "./pod.api";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
@ -22,22 +20,6 @@ export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForPvc(pvc: PersistentVolumeClaim): Promise<PersistentVolumeClaimMetricData> {
|
||||
const opts = { category: "pvc", pvc: pvc.getName(), namespace: pvc.getNs() };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
diskUsage: opts,
|
||||
diskCapacity: opts,
|
||||
}, {
|
||||
namespace: opts.namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface PersistentVolumeClaimMetricData extends Partial<Record<string, MetricData>> {
|
||||
diskUsage: MetricData;
|
||||
diskCapacity: MetricData;
|
||||
}
|
||||
|
||||
export interface PersistentVolumeClaimSpec {
|
||||
accessModes?: string[];
|
||||
dataSource?: TypedLocalObjectReference;
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { MetricData } from "./metrics.api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
@ -33,41 +31,6 @@ export class PodApi extends KubeApi<Pod> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<PodMetricData> {
|
||||
const podSelector = pods.map(pod => pod.getName()).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
cpuRequests: opts,
|
||||
cpuLimits: opts,
|
||||
memoryUsage: opts,
|
||||
memoryRequests: opts,
|
||||
memoryLimits: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface PodMetricData extends Partial<Record<string, MetricData>> {
|
||||
cpuUsage: MetricData;
|
||||
memoryUsage: MetricData;
|
||||
fsUsage: MetricData;
|
||||
fsWrites: MetricData;
|
||||
fsReads: MetricData;
|
||||
networkReceive: MetricData;
|
||||
networkTransmit: MetricData;
|
||||
cpuRequests?: MetricData;
|
||||
cpuLimits?: MetricData;
|
||||
memoryRequests?: MetricData;
|
||||
memoryLimits?: MetricData;
|
||||
}
|
||||
|
||||
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
|
||||
export interface PodLogsQuery {
|
||||
container?: string;
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData } from "./pod.api";
|
||||
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodTemplateSpec } from "./types/pod-template-spec";
|
||||
@ -41,23 +39,6 @@ export class ReplicaSetApi extends KubeApi<ReplicaSet> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForReplicaSets(replicasets: ReplicaSet[], namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const podSelector = replicasets.map(replicaset => `${replicaset.getName()}-[[:alnum:]]{5}`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ReplicaSetSpec {
|
||||
replicas?: number;
|
||||
selector: LabelSelector;
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import { apiBase } from "../index";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
export const annotations = [
|
||||
"kubectl.kubernetes.io/last-applied-configuration",
|
||||
];
|
||||
|
||||
export async function update(resource: object | string): Promise<KubeJsonApiData> {
|
||||
if (typeof resource === "string") {
|
||||
const parsed = yaml.load(resource);
|
||||
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
throw new Error("Cannot update resource to string or number");
|
||||
}
|
||||
|
||||
resource = parsed;
|
||||
}
|
||||
|
||||
return apiBase.post<KubeJsonApiData>("/stack", { data: resource });
|
||||
}
|
||||
|
||||
export async function patch(name: string, kind: string, ns: string | undefined, patch: Patch): Promise<KubeJsonApiData> {
|
||||
return apiBase.patch<KubeJsonApiData>("/stack", {
|
||||
data: {
|
||||
name,
|
||||
kind,
|
||||
ns,
|
||||
patch,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Patch } from "rfc6902";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import type { KubeJsonApiData } from "../../kube-json-api";
|
||||
|
||||
export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise<KubeJsonApiData>;
|
||||
|
||||
const requestKubeObjectPatchInjectable = getInjectable({
|
||||
id: "request-kube-object-patch",
|
||||
instantiate: (di): RequestKubeObjectPatch => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (name, kind, ns, patch) => (
|
||||
apiBase.patch("/stack", {
|
||||
data: {
|
||||
name,
|
||||
kind,
|
||||
ns,
|
||||
patch,
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export default requestKubeObjectPatchInjectable;
|
||||
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { apiBaseInjectionToken } from "../../api-base";
|
||||
import type { KubeJsonApiData } from "../../kube-json-api";
|
||||
|
||||
export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise<KubeJsonApiData>;
|
||||
|
||||
const requestKubeObjectCreationInjectable = getInjectable({
|
||||
id: "request-kube-object-creation",
|
||||
instantiate: (di): RequestKubeObjectCreation => {
|
||||
const apiBase = di.inject(apiBaseInjectionToken);
|
||||
|
||||
return (data) => apiBase.post("/stack", { data });
|
||||
},
|
||||
});
|
||||
|
||||
export default requestKubeObjectCreationInjectable;
|
||||
@ -5,8 +5,6 @@
|
||||
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodMetricData } from "./pod.api";
|
||||
import type { LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import type { PodTemplateSpec } from "./types/pod-template-spec";
|
||||
@ -46,23 +44,6 @@ export class StatefulSetApi extends KubeApi<StatefulSet> {
|
||||
}
|
||||
}
|
||||
|
||||
export function getMetricsForStatefulSets(statefulSets: StatefulSet[], namespace: string, selector = ""): Promise<PodMetricData> {
|
||||
const podSelector = statefulSets.map(statefulset => `${statefulset.getName()}-[[:digit:]]+`).join("|");
|
||||
const opts = { category: "pods", pods: podSelector, namespace, selector };
|
||||
|
||||
return metricsApi.getMetrics({
|
||||
cpuUsage: opts,
|
||||
memoryUsage: opts,
|
||||
fsUsage: opts,
|
||||
fsWrites: opts,
|
||||
fsReads: opts,
|
||||
networkReceive: opts,
|
||||
networkTransmit: opts,
|
||||
}, {
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
|
||||
export interface StatefulSetSpec {
|
||||
serviceName: string;
|
||||
replicas: number;
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export { apiBase } from "./api-base";
|
||||
export { apiKube } from "./api-kube";
|
||||
@ -9,12 +9,12 @@ import { Agent as HttpAgent } from "http";
|
||||
import { Agent as HttpsAgent } from "https";
|
||||
import { merge } from "lodash";
|
||||
import type { Response, RequestInit } from "node-fetch";
|
||||
import fetch from "node-fetch";
|
||||
import { stringify } from "querystring";
|
||||
import type { Patch } from "rfc6902";
|
||||
import type { PartialDeep, ValueOf } from "type-fest";
|
||||
import { EventEmitter } from "../../common/event-emitter";
|
||||
import logger from "../../common/logger";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { Fetch } from "../fetch/fetch.injectable";
|
||||
import type { Defaulted } from "../utils";
|
||||
import { json } from "../utils";
|
||||
|
||||
@ -59,6 +59,11 @@ export type ParamsAndQuery<Params, Query> = (
|
||||
: Params & { query?: undefined }
|
||||
);
|
||||
|
||||
export interface JsonApiDependencies {
|
||||
fetch: Fetch;
|
||||
readonly logger: Logger;
|
||||
}
|
||||
|
||||
export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>> {
|
||||
static readonly reqInitDefault = {
|
||||
headers: {
|
||||
@ -71,7 +76,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
debug: false,
|
||||
};
|
||||
|
||||
constructor(public readonly config: JsonApiConfig, reqInit?: RequestInit) {
|
||||
constructor(protected readonly dependencies: JsonApiDependencies, public readonly config: JsonApiConfig, reqInit?: RequestInit) {
|
||||
this.config = Object.assign({}, JsonApi.configDefault, config);
|
||||
this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit);
|
||||
this.parseResponse = this.parseResponse.bind(this);
|
||||
@ -105,7 +110,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
|
||||
}
|
||||
|
||||
return fetch(reqUrl, reqInit);
|
||||
return this.dependencies.fetch(reqUrl, reqInit);
|
||||
}
|
||||
|
||||
get<OutData = Data, Query = QueryParams>(
|
||||
@ -177,7 +182,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
reqInit,
|
||||
};
|
||||
|
||||
const res = await fetch(reqUrl, reqInit);
|
||||
const res = await this.dependencies.fetch(reqUrl, reqInit);
|
||||
|
||||
return this.parseResponse<OutData>(res, infoLog);
|
||||
}
|
||||
@ -233,7 +238,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
|
||||
protected writeLog(log: JsonApiLog) {
|
||||
const { method, reqUrl, ...params } = log;
|
||||
|
||||
logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
|
||||
this.dependencies.logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,28 +5,25 @@
|
||||
|
||||
// Base class for building all kubernetes apis
|
||||
|
||||
import { isFunction, merge } from "lodash";
|
||||
import { merge } from "lodash";
|
||||
import { stringify } from "querystring";
|
||||
import { apiKubePrefix, isDevelopment } from "../../common/vars";
|
||||
import { apiBase, apiKube } from "./index";
|
||||
import { createKubeApiURL, parseKubeApi } from "./kube-api-parse";
|
||||
import type { KubeObjectConstructor, KubeJsonApiDataFor, KubeObjectMetadata } from "./kube-object";
|
||||
import { KubeObject, KubeStatus, isKubeStatusData } from "./kube-object";
|
||||
import byline from "byline";
|
||||
import type { IKubeWatchEvent } from "./kube-watch-event";
|
||||
import type { KubeJsonApiData } from "./kube-json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import type { KubeJsonApiData, KubeJsonApi } from "./kube-json-api";
|
||||
import type { Disposer } from "../utils";
|
||||
import { isDefined, noop, WrappedAbortController } from "../utils";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import type { AgentOptions } from "https";
|
||||
import { Agent } from "https";
|
||||
import type { RequestInit, Response } from "node-fetch";
|
||||
import type { Patch } from "rfc6902";
|
||||
import assert from "assert";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
import logger from "../logger";
|
||||
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
import autoRegistrationEmitterInjectable from "./api-manager/auto-registration-emitter.injectable";
|
||||
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import { apiKubeInjectionToken } from "./api-kube";
|
||||
import type AbortController from "abort-controller";
|
||||
|
||||
/**
|
||||
@ -145,146 +142,39 @@ export interface KubeApiResourceList {
|
||||
resources: KubeApiResource[];
|
||||
}
|
||||
|
||||
export interface ILocalKubeApiConfig {
|
||||
metadata: {
|
||||
uid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type PropagationPolicy = undefined | "Orphan" | "Foreground" | "Background";
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export interface IKubeApiCluster extends ILocalKubeApiConfig { }
|
||||
|
||||
export interface IRemoteKubeApiConfig {
|
||||
cluster: {
|
||||
server: string;
|
||||
caData?: string;
|
||||
skipTLSVerify?: boolean;
|
||||
};
|
||||
user: {
|
||||
token?: string | (() => Promise<string>);
|
||||
clientCertificateData?: string;
|
||||
clientKeyData?: string;
|
||||
};
|
||||
/**
|
||||
* Custom instance of https.agent to use for the requests
|
||||
*
|
||||
* @remarks the custom agent replaced default agent, options skipTLSVerify,
|
||||
* clientCertificateData, clientKeyData and caData are ignored.
|
||||
*/
|
||||
agent?: Agent;
|
||||
}
|
||||
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => Api): Api;
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>): KubeApi<Object>;
|
||||
|
||||
export function forCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(cluster: ILocalKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: (new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>) = KubeApi): KubeApi<Object> {
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
const request = new KubeJsonApi({
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDevelopment,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${cluster.metadata.uid}.localhost:${url.port}`,
|
||||
},
|
||||
});
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass as KubeObjectConstructor<Object, KubeJsonApiDataFor<Object>>,
|
||||
request,
|
||||
});
|
||||
}
|
||||
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => Api): Api;
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass?: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object>): KubeApi<Object>;
|
||||
|
||||
export function forRemoteCluster<
|
||||
Object extends KubeObject,
|
||||
Api extends KubeApi<Object>,
|
||||
Data extends KubeJsonApiDataFor<Object>,
|
||||
>(config: IRemoteKubeApiConfig, kubeClass: KubeObjectConstructor<Object, Data>, apiClass: new (apiOpts: KubeApiOptions<Object>) => KubeApi<Object> = KubeApi): KubeApi<Object> {
|
||||
const reqInit: RequestInit = {};
|
||||
const agentOptions: AgentOptions = {};
|
||||
|
||||
if (config.cluster.skipTLSVerify === true) {
|
||||
agentOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
if (config.user.clientCertificateData) {
|
||||
agentOptions.cert = config.user.clientCertificateData;
|
||||
}
|
||||
|
||||
if (config.user.clientKeyData) {
|
||||
agentOptions.key = config.user.clientKeyData;
|
||||
}
|
||||
|
||||
if (config.cluster.caData) {
|
||||
agentOptions.ca = config.cluster.caData;
|
||||
}
|
||||
|
||||
if (Object.keys(agentOptions).length > 0) {
|
||||
reqInit.agent = new Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (config.agent) {
|
||||
reqInit.agent = config.agent;
|
||||
}
|
||||
|
||||
const token = config.user.token;
|
||||
const request = new KubeJsonApi({
|
||||
serverAddress: config.cluster.server,
|
||||
apiBase: "",
|
||||
debug: isDevelopment,
|
||||
...(token ? {
|
||||
getRequestOptions: async () => ({
|
||||
headers: {
|
||||
"Authorization": `Bearer ${isFunction(token) ? await token() : token}`,
|
||||
},
|
||||
}),
|
||||
} : {}),
|
||||
}, reqInit);
|
||||
|
||||
if (!apiClass) {
|
||||
apiClass = KubeApi as new (apiOpts: KubeApiOptions<Object>) => Api;
|
||||
}
|
||||
|
||||
return new apiClass({
|
||||
objectConstructor: kubeClass as KubeObjectConstructor<Object, KubeJsonApiDataFor<Object>>,
|
||||
request,
|
||||
});
|
||||
}
|
||||
|
||||
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (data: IKubeWatchEvent<T>, error: any) => void;
|
||||
export type KubeApiWatchCallback<T extends KubeJsonApiData = KubeJsonApiData> = (data: IKubeWatchEvent<T> | null, error: KubeStatus | Response | null | any) => void;
|
||||
|
||||
export interface KubeApiWatchOptions<Object extends KubeObject, Data extends KubeJsonApiDataFor<Object>> {
|
||||
namespace: string;
|
||||
/**
|
||||
* If the resource is namespaced then the default is `"default"`
|
||||
*/
|
||||
namespace?: string;
|
||||
|
||||
/**
|
||||
* This will be called when either an error occurs or some data is received
|
||||
*/
|
||||
callback?: KubeApiWatchCallback<Data>;
|
||||
|
||||
/**
|
||||
* This is a way of aborting the request
|
||||
*/
|
||||
abortController?: AbortController;
|
||||
|
||||
/**
|
||||
* The ID used for tracking within logs
|
||||
*/
|
||||
watchId?: string;
|
||||
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
retry?: boolean;
|
||||
|
||||
// timeout in seconds
|
||||
/**
|
||||
* timeout in seconds
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
@ -368,7 +258,7 @@ export class KubeApi<
|
||||
constructor(opts: KubeApiOptions<Object, Data>) {
|
||||
const {
|
||||
objectConstructor,
|
||||
request = apiKube,
|
||||
request = asLegacyGlobalForExtensionApi(apiKubeInjectionToken),
|
||||
kind = objectConstructor.kind,
|
||||
isNamespaced,
|
||||
apiBase: fullApiPathname = objectConstructor.apiBase,
|
||||
@ -411,13 +301,14 @@ export class KubeApi<
|
||||
*/
|
||||
private async getLatestApiPrefixGroup() {
|
||||
// Note that this.fullApiPathname is the "full" url, whereas this.apiBase is parsed
|
||||
const apiBases = [this.fullApiPathname, this.objectConstructor.apiBase, ...this.fallbackApiBases ?? []];
|
||||
const rawApiBases = [
|
||||
this.fullApiPathname,
|
||||
this.objectConstructor.apiBase,
|
||||
...this.fallbackApiBases ?? [],
|
||||
].filter(isDefined);
|
||||
const apiBases = new Set(rawApiBases);
|
||||
|
||||
for (const apiUrl of apiBases) {
|
||||
if (!apiUrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Split e.g. "/apis/extensions/v1beta1/ingresses" to parts
|
||||
const { apiPrefix, apiGroup, apiVersionWithGroup, resource } = parseKubeApi(apiUrl);
|
||||
@ -502,18 +393,47 @@ export class KubeApi<
|
||||
});
|
||||
}
|
||||
|
||||
getUrl({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<KubeApiQueryParams>) {
|
||||
/**
|
||||
* This method differs from {@link formatUrlForNotListing} because this treats `""` as "all namespaces"
|
||||
* @param namespace The namespace to list in or `""` for all namespaces
|
||||
*/
|
||||
formatUrlForListing(namespace: string) {
|
||||
return createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
namespace: this.isNamespaced
|
||||
? namespace ?? "default"
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a URL pathname and query for acting upon a specific resource.
|
||||
*/
|
||||
formatUrlForNotListing(resource?: Partial<ResourceDescriptor>, query?: Partial<KubeApiQueryParams>): string;
|
||||
|
||||
formatUrlForNotListing({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<KubeApiQueryParams>) {
|
||||
const resourcePath = createKubeApiURL({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
resource: this.apiResource,
|
||||
namespace: this.isNamespaced ? namespace ?? "default" : undefined,
|
||||
namespace: this.isNamespaced
|
||||
? namespace || "default"
|
||||
: undefined,
|
||||
name,
|
||||
});
|
||||
|
||||
return resourcePath + (query ? `?${stringify(this.normalizeQuery(query))}` : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link formatUrlForNotListing} instead
|
||||
*/
|
||||
getUrl(resource?: Partial<ResourceDescriptor>, query?: Partial<KubeApiQueryParams>) {
|
||||
return this.formatUrlForNotListing(resource, query);
|
||||
}
|
||||
|
||||
protected normalizeQuery(query: Partial<KubeApiQueryParams> = {}) {
|
||||
if (query.labelSelector) {
|
||||
query.labelSelector = [query.labelSelector].flat().join(",");
|
||||
@ -591,7 +511,7 @@ export class KubeApi<
|
||||
async list({ namespace = "", reqInit }: KubeApiListOptions = {}, query?: KubeApiQueryParams): Promise<Object[] | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const url = this.getUrl({ namespace });
|
||||
const url = this.formatUrlForListing(namespace);
|
||||
const res = await this.request.get(url, { query }, reqInit);
|
||||
const parsed = this.parseResponse(res, namespace);
|
||||
|
||||
@ -609,7 +529,7 @@ export class KubeApi<
|
||||
async get(desc: ResourceDescriptor, query?: KubeApiQueryParams): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const url = this.getUrl(desc);
|
||||
const url = this.formatUrlForNotListing(desc);
|
||||
const res = await this.request.get(url, { query });
|
||||
const parsed = this.parseResponse(res);
|
||||
|
||||
@ -623,7 +543,7 @@ export class KubeApi<
|
||||
async create({ name, namespace }: Partial<ResourceDescriptor>, partialData?: PartialDeep<Object>): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
|
||||
const apiUrl = this.getUrl({ namespace });
|
||||
const apiUrl = this.formatUrlForNotListing({ namespace });
|
||||
const data = merge(partialData, {
|
||||
kind: this.kind,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
@ -644,7 +564,7 @@ export class KubeApi<
|
||||
|
||||
async update({ name, namespace }: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl({ namespace, name });
|
||||
const apiUrl = this.formatUrlForNotListing({ namespace, name });
|
||||
|
||||
const res = await this.request.put(apiUrl, {
|
||||
data: merge(data, {
|
||||
@ -663,9 +583,13 @@ export class KubeApi<
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async patch(desc: ResourceDescriptor, data: PartialDeep<Object>): Promise<Object | null>;
|
||||
async patch(desc: ResourceDescriptor, data: PartialDeep<Object>, strategy: "strategic" | "merge"): Promise<Object | null>;
|
||||
async patch(desc: ResourceDescriptor, data: Patch, strategy: "json"): Promise<Object | null>;
|
||||
async patch(desc: ResourceDescriptor, data: PartialDeep<Object> | Patch, strategy: KubeApiPatchType): Promise<Object | null>;
|
||||
async patch(desc: ResourceDescriptor, data: PartialDeep<Object> | Patch, strategy: KubeApiPatchType = "strategic"): Promise<Object | null> {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl(desc);
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
const res = await this.request.patch(apiUrl, { data }, {
|
||||
headers: {
|
||||
@ -683,7 +607,7 @@ export class KubeApi<
|
||||
|
||||
async delete({ propagationPolicy = "Background", ...desc }: DeleteResourceDescriptor) {
|
||||
await this.checkPreferredVersion();
|
||||
const apiUrl = this.getUrl(desc);
|
||||
const apiUrl = this.formatUrlForNotListing(desc);
|
||||
|
||||
return this.request.del(apiUrl, {
|
||||
query: {
|
||||
@ -692,22 +616,27 @@ export class KubeApi<
|
||||
});
|
||||
}
|
||||
|
||||
getWatchUrl(namespace = "", query: KubeApiQueryParams = {}) {
|
||||
return this.getUrl({ namespace }, {
|
||||
getWatchUrl(namespace?: string, query: KubeApiQueryParams = {}) {
|
||||
return this.formatUrlForNotListing({ namespace }, {
|
||||
watch: 1,
|
||||
resourceVersion: this.getResourceVersion(namespace),
|
||||
...query,
|
||||
});
|
||||
}
|
||||
|
||||
watch(opts: KubeApiWatchOptions<Object, Data> = { namespace: "", retry: false }): () => void {
|
||||
watch(opts?: KubeApiWatchOptions<Object, Data>): () => void {
|
||||
let errorReceived = false;
|
||||
let timedRetry: NodeJS.Timeout;
|
||||
const { namespace, callback = noop, retry, timeout } = opts;
|
||||
const { watchId = `${this.kind.toLowerCase()}-${this.watchId++}` } = opts;
|
||||
const {
|
||||
namespace,
|
||||
callback = noop as KubeApiWatchCallback<Data>,
|
||||
retry = false,
|
||||
timeout,
|
||||
watchId = `${this.kind.toLowerCase()}-${this.watchId++}`,
|
||||
} = opts ?? {};
|
||||
|
||||
// Create AbortController for this request
|
||||
const abortController = new WrappedAbortController(opts.abortController);
|
||||
const abortController = new WrappedAbortController(opts?.abortController);
|
||||
|
||||
abortController.signal.addEventListener("abort", () => {
|
||||
logger.info(`[KUBE-API] watch (${watchId}) aborted ${watchUrl}`);
|
||||
@ -778,7 +707,7 @@ export class KubeApi<
|
||||
|
||||
byline(response.body).on("data", (line) => {
|
||||
try {
|
||||
const event = JSON.parse(line) as IKubeWatchEvent<KubeJsonApiData<KubeObjectMetadata>>;
|
||||
const event = JSON.parse(line) as IKubeWatchEvent<Data>;
|
||||
|
||||
if (event.type === "ERROR" && isKubeStatusData(event.object)) {
|
||||
errorReceived = true;
|
||||
|
||||
@ -6,8 +6,6 @@
|
||||
import type { JsonApiData, JsonApiError } from "./json-api";
|
||||
import { JsonApi } from "./json-api";
|
||||
import type { Response } from "node-fetch";
|
||||
import { apiKubePrefix, isDebugging } from "../vars";
|
||||
import { apiBase } from "./api-base";
|
||||
import type { KubeJsonApiObjectMetadata } from "./kube-object";
|
||||
|
||||
export interface KubeJsonApiListMetadata {
|
||||
@ -47,20 +45,6 @@ export interface KubeJsonApiError extends JsonApiError {
|
||||
}
|
||||
|
||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||
static forCluster(clusterId: string): KubeJsonApi {
|
||||
const url = new URL(apiBase.config.serverAddress);
|
||||
|
||||
return new this({
|
||||
serverAddress: apiBase.config.serverAddress,
|
||||
apiBase: apiKubePrefix,
|
||||
debug: isDebugging,
|
||||
}, {
|
||||
headers: {
|
||||
"Host": `${clusterId}.localhost:${url.port}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected parseError(error: KubeJsonApiError | string, res: Response): string[] {
|
||||
if (typeof error === "string") {
|
||||
return [error];
|
||||
|
||||
@ -9,11 +9,15 @@ import moment from "moment";
|
||||
import type { KubeJsonApiData, KubeJsonApiDataList, KubeJsonApiListMetadata } from "./kube-json-api";
|
||||
import { autoBind, formatDuration, hasOptionalTypedProperty, hasTypedProperty, isObject, isString, isNumber, bindPredicate, isTypedArray, isRecord, json } from "../utils";
|
||||
import type { ItemObject } from "../item.store";
|
||||
import { apiKube } from "./index";
|
||||
import * as resourceApplierApi from "./endpoints/resource-applier.api";
|
||||
import type { Patch } from "rfc6902";
|
||||
import assert from "assert";
|
||||
import type { JsonObject } from "type-fest";
|
||||
import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api";
|
||||
import requestKubeObjectPatchInjectable from "./endpoints/resource-applier.api/request-patch.injectable";
|
||||
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
|
||||
import { apiKubeInjectionToken } from "./api-kube";
|
||||
import requestKubeObjectCreationInjectable from "./endpoints/resource-applier.api/request-update.injectable";
|
||||
import { dump } from "js-yaml";
|
||||
|
||||
export type KubeJsonApiDataFor<K> = K extends KubeObject<infer Metadata, infer Status, infer Spec>
|
||||
? KubeJsonApiData<Metadata, Status, Spec>
|
||||
@ -375,6 +379,12 @@ export type ScopedNamespace<Namespaced extends KubeObjectScope> = (
|
||||
: string | undefined
|
||||
);
|
||||
|
||||
const resourceApplierAnnotationsForFiltering = [
|
||||
"kubectl.kubernetes.io/last-applied-configuration",
|
||||
];
|
||||
|
||||
const filterOutResourceApplierAnnotations = (label: string) => !resourceApplierAnnotationsForFiltering.some(key => label.startsWith(key));
|
||||
|
||||
export class KubeObject<
|
||||
Metadata extends KubeObjectMetadata<KubeObjectScope> = KubeObjectMetadata<KubeObjectScope>,
|
||||
Status = unknown,
|
||||
@ -588,11 +598,11 @@ export class KubeObject<
|
||||
getAnnotations(filter = false): string[] {
|
||||
const labels = KubeObject.stringifyLabels(this.metadata.annotations);
|
||||
|
||||
return filter ? labels.filter(label => {
|
||||
const skip = resourceApplierApi.annotations.some(key => label.startsWith(key));
|
||||
if (!filter) {
|
||||
return labels;
|
||||
}
|
||||
|
||||
return !skip;
|
||||
}) : labels;
|
||||
return labels.filter(filterOutResourceApplierAnnotations);
|
||||
}
|
||||
|
||||
getOwnerRefs() {
|
||||
@ -634,7 +644,9 @@ export class KubeObject<
|
||||
}
|
||||
}
|
||||
|
||||
return resourceApplierApi.patch(this.getName(), this.kind, this.getNs(), patch);
|
||||
const requestKubeObjectPatch = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectPatchInjectable);
|
||||
|
||||
return requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -647,11 +659,13 @@ export class KubeObject<
|
||||
* @deprecated use KubeApi.update instead
|
||||
*/
|
||||
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
||||
// use unified resource-applier api for updating all k8s objects
|
||||
return resourceApplierApi.update({
|
||||
const requestKubeObjectCreation = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectCreationInjectable);
|
||||
const descriptor = dump({
|
||||
...this.toPlainObject(),
|
||||
...data,
|
||||
});
|
||||
|
||||
return requestKubeObjectCreation(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -660,6 +674,8 @@ export class KubeObject<
|
||||
delete(params?: object) {
|
||||
assert(this.selfLink, "selfLink must be present to delete self");
|
||||
|
||||
const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken);
|
||||
|
||||
return apiKube.del(this.selfLink, params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import windowLocationInjectable from "./window-location.injectable";
|
||||
|
||||
export default getGlobalOverride(windowLocationInjectable, () => ({
|
||||
host: "localhost",
|
||||
port: "12345",
|
||||
}));
|
||||
17
src/common/k8s-api/window-location.injectable.ts
Normal file
17
src/common/k8s-api/window-location.injectable.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
const windowLocationInjectable = getInjectable({
|
||||
id: "window-location",
|
||||
instantiate: () => {
|
||||
const { host, port } = window.location;
|
||||
|
||||
return { host, port };
|
||||
},
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default windowLocationInjectable;
|
||||
@ -4,31 +4,14 @@
|
||||
*/
|
||||
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import fse from "fs-extra";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import yaml from "js-yaml";
|
||||
import logger from "../main/logger";
|
||||
import type { Cluster, Context, User } from "@kubernetes/client-node/dist/config_types";
|
||||
import { newClusters, newContexts, newUsers } from "@kubernetes/client-node/dist/config_types";
|
||||
import { isDefined, resolvePath } from "./utils";
|
||||
import { isDefined } from "./utils";
|
||||
import Joi from "joi";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
|
||||
export const kubeConfigDefaultPath = path.join(os.homedir(), ".kube", "config");
|
||||
|
||||
export function loadConfigFromFileSync(filePath: string): ConfigResult {
|
||||
const content = fse.readFileSync(resolvePath(filePath), "utf-8");
|
||||
|
||||
return loadConfigFromString(content);
|
||||
}
|
||||
|
||||
export async function loadConfigFromFile(filePath: string): Promise<ConfigResult> {
|
||||
const content = await fse.readFile(resolvePath(filePath), "utf-8");
|
||||
|
||||
return loadConfigFromString(content);
|
||||
}
|
||||
|
||||
const clusterSchema = Joi.object({
|
||||
name: Joi
|
||||
.string()
|
||||
|
||||
23
src/common/kube-helpers/load-config-from-file.injectable.ts
Normal file
23
src/common/kube-helpers/load-config-from-file.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 { getInjectable } from "@ogre-tools/injectable";
|
||||
import readFileInjectable from "../fs/read-file.injectable";
|
||||
import type { ConfigResult } from "../kube-helpers";
|
||||
import { loadConfigFromString } from "../kube-helpers";
|
||||
import resolveTildeInjectable from "../path/resolve-tilde.injectable";
|
||||
|
||||
export type LoadConfigfromFile = (filePath: string) => Promise<ConfigResult>;
|
||||
|
||||
const loadConfigfromFileInjectable = getInjectable({
|
||||
id: "load-configfrom-file",
|
||||
instantiate: (di): LoadConfigfromFile => {
|
||||
const readFile = di.inject(readFileInjectable);
|
||||
const resolveTilde = di.inject(resolveTildeInjectable);
|
||||
|
||||
return async (filePath) => loadConfigFromString(await readFile(resolveTilde(filePath)));
|
||||
},
|
||||
});
|
||||
|
||||
export default loadConfigfromFileInjectable;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import homeDirectoryPathInjectable from "./home-directory-path.injectable";
|
||||
|
||||
export default getGlobalOverride(homeDirectoryPathInjectable, () => "/some-home-directory");
|
||||
14
src/common/os/home-directory-path.injectable.ts
Normal file
14
src/common/os/home-directory-path.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { homedir } from "os";
|
||||
|
||||
const homeDirectoryPathInjectable = getInjectable({
|
||||
id: "home-directory-path",
|
||||
instantiate: () => homedir(),
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default homeDirectoryPathInjectable;
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import tempDirectoryPathInjectable from "./temp-directory-path.injectable";
|
||||
|
||||
export default getGlobalOverride(tempDirectoryPathInjectable, () => "/some-temp-directory");
|
||||
14
src/common/os/temp-directory-path.injectable.ts
Normal file
14
src/common/os/temp-directory-path.injectable.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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 { tmpdir } from "os";
|
||||
|
||||
const tempDirectoryPathInjectable = getInjectable({
|
||||
id: "temp-directory-path",
|
||||
instantiate: () => tmpdir(),
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default tempDirectoryPathInjectable;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user