1
0
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:
Sebastian Malton 2022-10-05 08:10:36 -04:00 committed by GitHub
parent ed073d6562
commit 76066c5ebf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
367 changed files with 16701 additions and 5302 deletions

View File

@ -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(),
});
}

View File

@ -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",

View File

@ -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");
},
});

View File

@ -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");
},
});

View File

@ -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");
},
});

View File

@ -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);
},
});

View 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;

View File

@ -9,7 +9,6 @@ import type { KubeConfig } from "@kubernetes/client-node";
import { HttpError } from "@kubernetes/client-node";
import type { Kubectl } from "../../main/kubectl/kubectl";
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager";
import { loadConfigFromFile } from "../kube-helpers";
import type { KubeApiResource, KubeResource } from "../rbac";
import { apiResourceRecord, apiResources } from "../rbac";
import type { VersionDetector } from "../../main/cluster-detectors/version-detector";
@ -25,6 +24,7 @@ import type { ListNamespaces } from "./list-namespaces.injectable";
import assert from "assert";
import type { Logger } from "../logger";
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
export interface ClusterDependencies {
readonly directoryForKubeConfigs: string;
@ -37,6 +37,7 @@ export interface ClusterDependencies {
createListNamespaces: (config: KubeConfig) => ListNamespaces;
createVersionDetector: (cluster: Cluster) => VersionDetector;
broadcastMessage: BroadcastMessage;
loadConfigfromFile: LoadConfigfromFile;
}
/**
@ -500,7 +501,7 @@ export class Cluster implements ClusterModel, ClusterState {
}
async getKubeconfig(): Promise<KubeConfig> {
const { config } = await loadConfigFromFile(this.kubeConfigPath);
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
return config;
}
@ -510,7 +511,7 @@ export class Cluster implements ClusterModel, ClusterState {
*/
async getProxyKubeconfig(): Promise<KubeConfig> {
const proxyKCPath = await this.getProxyKubeconfigPath();
const { config } = await loadConfigFromFile(proxyKCPath);
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
return config;
}

View File

@ -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",
);

View 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");
});

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { RequestInfo, RequestInit, Response } from "node-fetch";
import fetch from "node-fetch";
export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
const fetchInjectable = getInjectable({
id: "fetch",
instantiate: (): Fetch => fetch,
causesSideEffects: true,
});
export default fetchInjectable;

View 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");
});

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type AccessPath = (path: string, mode?: number) => Promise<boolean>;
const accessPathInjectable = getInjectable({
id: "access-path",
instantiate: (di): AccessPath => {
const { access } = di.inject(fsInjectable);
return async (path, mode) => {
try {
await access(path, mode);
return true;
} catch {
return false;
}
};
},
});
export default accessPathInjectable;

View 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");
});

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { CopyOptions } from "fs-extra";
import fsInjectable from "./fs.injectable";
export type Copy = (src: string, dest: string, options?: CopyOptions | undefined) => Promise<void>;
const copyInjectable = getInjectable({
id: "copy",
instantiate: (di): Copy => di.inject(fsInjectable).copy,
});
export default copyInjectable;

View 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");
});

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type DeleteFile = (filePath: string) => Promise<void>;
const deleteFileInjectable = getInjectable({
id: "delete-file",
instantiate: (di): DeleteFile => di.inject(fsInjectable).unlink,
});
export default deleteFileInjectable;

View 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");
});

View File

@ -5,14 +5,14 @@
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type EnsureDirectory = (dirPath: string) => Promise<void>;
const ensureDirInjectable = getInjectable({
id: "ensure-dir",
// TODO: Remove usages of ensureDir from business logic.
// TODO: Read, Write, Watch etc. operations should do this internally.
instantiate: (di) => di.inject(fsInjectable).ensureDir,
causesSideEffects: true,
instantiate: (di): EnsureDirectory => di.inject(fsInjectable).ensureDir,
});
export default ensureDirInjectable;

View 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");
});

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { ExtractOptions } from "tar";
import { extract } from "tar";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
export type ExtractTar = (filePath: string, opts?: ExtractOptions) => Promise<void>;
const extractTarInjectable = getInjectable({
id: "extract-tar",
instantiate: (di): ExtractTar => {
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return (filePath, opts = {}) => extract({
file: filePath,
cwd: getDirnameOfPath(filePath),
...opts,
});
},
causesSideEffects: true,
});
export default extractTarInjectable;

View 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");
});

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { Stats } from "fs";
import fsInjectable from "./fs.injectable";
export type LStat = (path: string) => Promise<Stats>;
const lstatInjectable = getInjectable({
id: "lstat",
instantiate: (di): LStat => di.inject(fsInjectable).lstat,
});
export default lstatInjectable;

View 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");
});

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { MoveOptions } from "fs-extra";
import fsInjectable from "./fs.injectable";
export type Move = (src: string, dest: string, options?: MoveOptions) => Promise<void>;
const moveInjectable = getInjectable({
id: "move",
instantiate: (di): Move => di.inject(fsInjectable).move,
});
export default moveInjectable;

View 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");
});

View 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 readDirectoryInjectable from "./read-directory.injectable";
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
throw new Error("tried to read a directory's content without override");
});

View 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;

View File

@ -5,11 +5,16 @@
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type ReadFile = (filePath: string) => Promise<string>;
const readFileInjectable = getInjectable({
id: "read-file",
instantiate: (di) => (filePath: string) =>
di.inject(fsInjectable).readFile(filePath, "utf-8"),
instantiate: (di): ReadFile => {
const { readFile } = di.inject(fsInjectable);
return (filePath) => readFile(filePath, "utf-8");
},
});
export default readFileInjectable;

View 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");
});

View File

@ -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;

View 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");
});

View File

@ -3,20 +3,28 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import path from "path";
import type { WriteFileOptions } from "fs-extra";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
import fsInjectable from "./fs.injectable";
export type WriteFile = (filePath: string, content: string | Buffer, opts?: WriteFileOptions) => Promise<void>;
const writeFileInjectable = getInjectable({
id: "write-file",
instantiate: (di) => {
instantiate: (di): WriteFile => {
const { writeFile, ensureDir } = di.inject(fsInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return async (filePath: string, content: string | Buffer) => {
await ensureDir(path.dirname(filePath), { mode: 0o755 });
return async (filePath, content, opts) => {
await ensureDir(getDirnameOfPath(filePath), {
mode: 0o755,
...(opts ?? {}),
});
await writeFile(filePath, content, {
encoding: "utf-8",
...(opts ?? {}),
});
};
},

View File

@ -3,37 +3,27 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { EnsureOptions, WriteOptions } from "fs-extra";
import path from "path";
import type { JsonValue } from "type-fest";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
import fsInjectable from "./fs.injectable";
export type WriteJson = (filePath: string, contents: JsonValue) => Promise<void>;
interface Dependencies {
writeJson: (file: string, object: any, options?: WriteOptions | BufferEncoding | string) => Promise<void>;
ensureDir: (dir: string, options?: EnsureOptions | number) => Promise<void>;
}
const 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,
});
},
});

View File

@ -8,9 +8,6 @@ export const clusterSetFrameIdHandler = "cluster:set-frame-id";
export const clusterVisibilityHandler = "cluster:visibility";
export const clusterRefreshHandler = "cluster:refresh";
export const clusterDisconnectHandler = "cluster:disconnect";
export const clusterDeleteHandler = "cluster:delete";
export const clusterSetDeletingHandler = "cluster:deleting:set";
export const clusterClearDeletingHandler = "cluster:deleting:clear";
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all";
export const clusterKubectlDeleteAllHandler = "cluster:kubectl-delete-all";
export const clusterStates = "cluster:states";

File diff suppressed because it is too large Load Diff

View File

@ -3,38 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { JsonApi } from "./json-api";
import { apiPrefix, isDebugging, isDevelopment } from "../vars";
import { appEventBus } from "../app-event-bus/event-bus";
import type { JsonApi } from "./json-api";
import { getInjectionToken } from "@ogre-tools/injectable";
export let apiBase: JsonApi;
if (typeof window === "undefined") {
appEventBus.addListener((event) => {
if (event.name !== "lens-proxy" && event.action !== "listen") return;
const params = event.params as { port?: number };
if (!params.port) return;
apiBase = new JsonApi({
serverAddress: `http://127.0.0.1:${params.port}`,
apiBase: apiPrefix,
debug: isDevelopment || isDebugging,
}, {
headers: {
"Host": `localhost:${params.port}`,
},
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,
},
});
}

View File

@ -4,11 +4,8 @@
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api";
import type { KubeJsonApi } from "./kube-json-api";
export const apiKubeInjectionToken = getInjectionToken<KubeJsonApi>({
id: "api-kube-injection-token",
});
export const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken);

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { 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;

View 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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { 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;

View File

@ -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?: {

View File

@ -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,
});
}

View File

@ -7,8 +7,7 @@ import moment from "moment";
import type { DerivedKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData, PodSpec } from "./pod.api";
import type { PodSpec } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isNumber, isObject } from "../../utils";
@ -70,23 +69,6 @@ export class DeploymentApi extends KubeApi<Deployment> {
}
}
export function getMetricsForDeployments(deployments: Deployment[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = deployments.map(deployment => `${deployment.getName()}-[[:alnum:]]{9,}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface DeploymentSpec {
replicas: number;
selector: LabelSelector;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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;

View File

@ -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;

View File

@ -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;

View 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 requestHelmReleaseConfigurationInjectable from "./request-configuration.injectable";
export default getGlobalOverride(requestHelmReleaseConfigurationInjectable, () => () => {
throw new Error("Tried to call requestHelmReleaseConfiguration with no override");
});

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } 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;

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -5,8 +5,7 @@
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { metricsApi } from "./metrics.api";
import type { PodMetricData, PodSpec } from "./pod.api";
import type { PodSpec } from "./pod.api";
import type { Container } from "./types/container";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
@ -103,20 +102,3 @@ export class JobApi extends KubeApi<Job> {
});
}
}
export function getMetricsForJobs(jobs: Job[], namespace: string, selector = ""): Promise<PodMetricData> {
const podSelector = jobs.map(job => `${job.getName()}-[[:alnum:]]{5}`).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
memoryUsage: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}

View File

@ -6,7 +6,7 @@
// Metrics api
import moment from "moment";
import { apiBase } from "../index";
import { isDefined, object } from "../../utils";
export interface MetricData {
status: string;
@ -29,63 +29,6 @@ export interface MetricResult {
values: [number, string][];
}
export interface MetricProviderInfo {
name: string;
id: string;
isConfigurable: boolean;
}
export interface IMetricsReqParams {
start?: number | string; // timestamp in seconds or valid date-string
end?: number | string;
step?: number; // step in seconds (default: 60s = each point 1m)
range?: number; // time-range in seconds for data aggregation (default: 3600s = last 1h)
namespace?: string; // rbac-proxy validation param
}
export interface IResourceMetrics<T extends MetricData> {
[metric: string]: T;
cpuUsage: T;
memoryUsage: T;
fsUsage: T;
fsWrites: T;
fsReads: T;
networkReceive: T;
networkTransmit: T;
}
async function getMetrics(query: string, reqParams?: IMetricsReqParams): Promise<MetricData>;
async function getMetrics(query: string[], reqParams?: IMetricsReqParams): Promise<MetricData[]>;
async function getMetrics<MetricNames extends string>(query: Record<MetricNames, Partial<Record<string, string>>>, reqParams?: IMetricsReqParams): Promise<Record<MetricNames, MetricData>>;
async function getMetrics(query: string | string[] | Partial<Record<string, Partial<Record<string, string>>>>, reqParams: IMetricsReqParams = {}): Promise<MetricData | MetricData[] | Partial<Record<string, MetricData>>> {
const { range = 3600, step = 60, namespace } = reqParams;
let { start, end } = reqParams;
if (!start && !end) {
const timeNow = Date.now() / 1000;
const now = moment.unix(timeNow).startOf("minute").unix(); // round date to minutes
start = now - range;
end = now;
}
return apiBase.post("/metrics", {
data: query,
query: {
start, end, step,
"kubernetes_namespace": namespace,
},
});
}
export const metricsApi = {
getMetrics,
async getMetricProviders(): Promise<MetricProviderInfo[]> {
return apiBase.get("/metrics/providers");
},
};
export function normalizeMetrics(metrics: MetricData | undefined | null, frames = 60): MetricData {
if (!metrics?.data?.result) {
return {
@ -145,7 +88,7 @@ export function isMetricsEmpty(metrics: Partial<Record<string, MetricData>>) {
return Object.values(metrics).every(metric => !metric?.data?.result?.length);
}
export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | null | undefined, itemName: string): Partial<Record<string, MetricData>> | undefined {
export function getItemMetrics<Keys extends string>(metrics: Partial<Record<Keys, MetricData>> | null | undefined, itemName: string): Partial<Record<Keys, MetricData>> | undefined {
if (!metrics) {
return undefined;
}
@ -166,22 +109,16 @@ export function getItemMetrics(metrics: Partial<Record<string, MetricData>> | nu
return itemMetrics;
}
export function getMetricLastPoints<T extends Partial<Record<string, MetricData>>>(metrics: T): Record<keyof T, number> {
const result: Partial<{ [metric: string]: number }> = {};
Object.keys(metrics).forEach(metricName => {
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),
);
}

View 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 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { 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;

View File

@ -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,
});
}

View File

@ -6,8 +6,6 @@
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils";
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
import { KubeApi } from "../kube-api";
import { TypedRegEx } from "typed-regex";
@ -21,32 +19,6 @@ export class NodeApi extends KubeApi<Node> {
}
}
export function getMetricsForAllNodes(): Promise<NodeMetricData> {
const opts = { category: "nodes" };
return metricsApi.getMetrics({
memoryUsage: opts,
workloadMemoryUsage: opts,
memoryCapacity: opts,
memoryAllocatableCapacity: opts,
cpuUsage: opts,
cpuCapacity: opts,
fsSize: opts,
fsUsage: opts,
});
}
export interface NodeMetricData extends Partial<Record<string, MetricData>> {
memoryUsage: MetricData;
workloadMemoryUsage: MetricData;
memoryCapacity: MetricData;
memoryAllocatableCapacity: MetricData;
cpuUsage: MetricData;
cpuCapacity: MetricData;
fsUsage: MetricData;
fsSize: MetricData;
}
export interface NodeTaint {
key: string;
value?: string;

View File

@ -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;

View File

@ -3,8 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MetricData } from "./metrics.api";
import { metricsApi } from "./metrics.api";
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
@ -33,41 +31,6 @@ export class PodApi extends KubeApi<Pod> {
}
}
export function getMetricsForPods(pods: Pod[], namespace: string, selector = "pod, namespace"): Promise<PodMetricData> {
const podSelector = pods.map(pod => pod.getName()).join("|");
const opts = { category: "pods", pods: podSelector, namespace, selector };
return metricsApi.getMetrics({
cpuUsage: opts,
cpuRequests: opts,
cpuLimits: opts,
memoryUsage: opts,
memoryRequests: opts,
memoryLimits: opts,
fsUsage: opts,
fsWrites: opts,
fsReads: opts,
networkReceive: opts,
networkTransmit: opts,
}, {
namespace,
});
}
export interface PodMetricData extends Partial<Record<string, MetricData>> {
cpuUsage: MetricData;
memoryUsage: MetricData;
fsUsage: MetricData;
fsWrites: MetricData;
fsReads: MetricData;
networkReceive: MetricData;
networkTransmit: MetricData;
cpuRequests?: MetricData;
cpuLimits?: MetricData;
memoryRequests?: MetricData;
memoryLimits?: MetricData;
}
// Reference: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-log-pod-v1-core
export interface PodLogsQuery {
container?: string;

View File

@ -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;

View File

@ -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,
},
});
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
export { apiBase } from "./api-base";
export { apiKube } from "./api-kube";

View File

@ -9,12 +9,12 @@ import { Agent as HttpAgent } from "http";
import { Agent as HttpsAgent } from "https";
import { merge } from "lodash";
import type { Response, RequestInit } from "node-fetch";
import fetch from "node-fetch";
import { stringify } from "querystring";
import type { Patch } from "rfc6902";
import type { PartialDeep, ValueOf } from "type-fest";
import { EventEmitter } from "../../common/event-emitter";
import logger from "../../common/logger";
import type { Logger } from "../../common/logger";
import type { Fetch } from "../fetch/fetch.injectable";
import type { Defaulted } from "../utils";
import { json } from "../utils";
@ -59,6 +59,11 @@ export type ParamsAndQuery<Params, Query> = (
: Params & { query?: undefined }
);
export interface JsonApiDependencies {
fetch: Fetch;
readonly logger: Logger;
}
export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = JsonApiParams<Data>> {
static readonly reqInitDefault = {
headers: {
@ -71,7 +76,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
debug: false,
};
constructor(public readonly config: JsonApiConfig, reqInit?: RequestInit) {
constructor(protected readonly dependencies: JsonApiDependencies, public readonly config: JsonApiConfig, reqInit?: RequestInit) {
this.config = Object.assign({}, JsonApi.configDefault, config);
this.reqInit = merge({}, JsonApi.reqInitDefault, reqInit);
this.parseResponse = this.parseResponse.bind(this);
@ -105,7 +110,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
reqUrl += (reqUrl.includes("?") ? "&" : "?") + queryString;
}
return fetch(reqUrl, reqInit);
return this.dependencies.fetch(reqUrl, reqInit);
}
get<OutData = Data, Query = QueryParams>(
@ -177,7 +182,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
reqInit,
};
const res = await fetch(reqUrl, reqInit);
const res = await this.dependencies.fetch(reqUrl, reqInit);
return this.parseResponse<OutData>(res, infoLog);
}
@ -233,7 +238,7 @@ export class JsonApi<Data = JsonApiData, Params extends JsonApiParams<Data> = Js
protected writeLog(log: JsonApiLog) {
const { method, reqUrl, ...params } = log;
logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
this.dependencies.logger.debug(`[JSON-API] request ${method} ${reqUrl}`, params);
}
}

View File

@ -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;

View File

@ -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];

View File

@ -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);
}
}

View File

@ -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",
}));

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
const windowLocationInjectable = getInjectable({
id: "window-location",
instantiate: () => {
const { host, port } = window.location;
return { host, port };
},
causesSideEffects: true,
});
export default windowLocationInjectable;

View File

@ -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()

View 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;

View File

@ -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");

View 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;

View File

@ -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");

View 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