mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into wrap-pod-logs
This commit is contained in:
commit
66dbcede1c
@ -16,7 +16,6 @@
|
||||
"dist/**/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"npm": "^8.5.3"
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions"
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"npm": "^8.5.3",
|
||||
"semver": "^7.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"npm": "^8.5.3"
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions"
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions",
|
||||
"npm": "^8.5.3"
|
||||
"@k8slens/extensions": "file:../../src/extensions/npm/extensions"
|
||||
}
|
||||
}
|
||||
|
||||
51
package.json
51
package.json
@ -218,11 +218,11 @@
|
||||
"@hapi/subtext": "^7.0.4",
|
||||
"@kubernetes/client-node": "^0.17.0",
|
||||
"@material-ui/styles": "^4.11.5",
|
||||
"@ogre-tools/fp": "9.0.3",
|
||||
"@ogre-tools/injectable": "9.0.3",
|
||||
"@ogre-tools/injectable-extension-for-auto-registration": "9.0.3",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "9.0.3",
|
||||
"@ogre-tools/injectable-react": "9.0.3",
|
||||
"@ogre-tools/fp": "10.1.0",
|
||||
"@ogre-tools/injectable": "10.1.0",
|
||||
"@ogre-tools/injectable-extension-for-auto-registration": "10.1.0",
|
||||
"@ogre-tools/injectable-extension-for-mobx": "10.1.0",
|
||||
"@ogre-tools/injectable-react": "10.1.0",
|
||||
"@sentry/electron": "^3.0.7",
|
||||
"@sentry/integrations": "^6.19.3",
|
||||
"@side/jest-runtime": "^1.0.1",
|
||||
@ -254,9 +254,9 @@
|
||||
"mac-ca": "^1.0.6",
|
||||
"marked": "^4.1.0",
|
||||
"md5-file": "^5.0.0",
|
||||
"mobx": "^6.6.1",
|
||||
"mobx": "^6.6.2",
|
||||
"mobx-observable-history": "^2.0.3",
|
||||
"mobx-react": "^7.5.2",
|
||||
"mobx-react": "^7.5.3",
|
||||
"mobx-utils": "^6.0.4",
|
||||
"mock-fs": "^5.1.4",
|
||||
"moment": "^2.29.4",
|
||||
@ -265,7 +265,7 @@
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-pty": "0.10.1",
|
||||
"npm": "^6.14.17",
|
||||
"npm": "^8.19.1",
|
||||
"p-limit": "^3.1.0",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
@ -278,7 +278,7 @@
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"rfc6902": "^4.0.2",
|
||||
"selfsigned": "^2.0.1",
|
||||
"selfsigned": "^2.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"shell-env": "^3.0.1",
|
||||
"spdy": "^4.0.2",
|
||||
@ -289,7 +289,7 @@
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^8.3.2",
|
||||
"win-ca": "^3.5.0",
|
||||
"winston": "^3.8.1",
|
||||
"winston": "^3.8.2",
|
||||
"winston-console-format": "^1.0.8",
|
||||
"winston-transport-browserconsole": "^1.0.5",
|
||||
"ws": "^8.8.1",
|
||||
@ -303,7 +303,7 @@
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||
"@sentry/types": "^6.19.7",
|
||||
"@swc/cli": "^0.1.57",
|
||||
"@swc/core": "^1.2.242",
|
||||
"@swc/core": "^1.2.249",
|
||||
"@swc/jest": "^0.2.22",
|
||||
"@testing-library/dom": "^7.31.2",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
@ -329,11 +329,11 @@
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/marked": "^4.0.6",
|
||||
"@types/marked": "^4.0.7",
|
||||
"@types/md5-file": "^4.0.2",
|
||||
"@types/mini-css-extract-plugin": "^2.4.0",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^16.11.55",
|
||||
"@types/node": "^16.11.58",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"@types/npm": "^2.0.32",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
@ -363,8 +363,8 @@
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@types/webpack-node-externals": "^2.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||
"@typescript-eslint/parser": "^5.36.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.2",
|
||||
"@typescript-eslint/parser": "^5.36.2",
|
||||
"adr": "^1.4.1",
|
||||
"ansi_up": "^5.1.0",
|
||||
"chart.js": "^2.9.4",
|
||||
@ -372,19 +372,19 @@
|
||||
"cli-progress": "^3.11.2",
|
||||
"color": "^3.2.1",
|
||||
"command-line-args": "^5.2.1",
|
||||
"concurrently": "^7.3.0",
|
||||
"concurrently": "^7.4.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"deepdash": "^5.3.9",
|
||||
"dompurify": "^2.4.0",
|
||||
"electron": "^19.0.13",
|
||||
"electron": "^19.0.16",
|
||||
"electron-builder": "^23.3.3",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"esbuild": "^0.15.6",
|
||||
"esbuild-loader": "^2.19.0",
|
||||
"esbuild": "^0.15.7",
|
||||
"esbuild-loader": "^2.20.0",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-react": "7.30.1",
|
||||
"eslint-plugin-react": "7.31.7",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"flex.box": "^3.4.4",
|
||||
@ -405,9 +405,10 @@
|
||||
"node-gyp": "^8.3.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"playwright": "^1.25.1",
|
||||
"playwright": "^1.25.2",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"query-string": "^7.1.1",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-refresh": "^0.14.0",
|
||||
@ -417,9 +418,9 @@
|
||||
"react-select-event": "^5.5.1",
|
||||
"react-table": "^7.8.0",
|
||||
"react-window": "^1.8.7",
|
||||
"sass": "^1.54.8",
|
||||
"sass": "^1.54.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sharp": "^0.30.7",
|
||||
"sharp": "^0.31.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"tar-stream": "^2.2.0",
|
||||
@ -427,13 +428,13 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"type-fest": "^2.14.0",
|
||||
"typed-emitter": "^1.4.0",
|
||||
"typedoc": "0.23.13",
|
||||
"typedoc": "0.23.14",
|
||||
"typedoc-plugin-markdown": "^3.13.1",
|
||||
"typescript": "^4.8.2",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.10.1",
|
||||
"webpack-dev-server": "^4.11.0",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"xterm": "^4.19.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* 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 emitEventInjectable from "./emit-event.injectable";
|
||||
|
||||
export default getGlobalOverride(emitEventInjectable, () => () => {});
|
||||
@ -9,6 +9,7 @@ const appEventBusInjectable = getInjectable({
|
||||
id: "app-event-bus",
|
||||
instantiate: () => appEventBus,
|
||||
causesSideEffects: true,
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
export default appEventBusInjectable;
|
||||
|
||||
@ -8,6 +8,7 @@ import appEventBusInjectable from "./app-event-bus.injectable";
|
||||
const emitEventInjectable = getInjectable({
|
||||
id: "emit-event",
|
||||
instantiate: (di) => di.inject(appEventBusInjectable).emit,
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
export default emitEventInjectable;
|
||||
|
||||
@ -59,10 +59,10 @@ export abstract class BaseStore<T extends object> extends Singleton {
|
||||
const getConfigurationFileModel = di.inject(getConfigurationFileModelInjectable);
|
||||
|
||||
this.storeConfig = getConfigurationFileModel({
|
||||
...this.params,
|
||||
projectName: "lens",
|
||||
projectVersion: di.inject(appVersionInjectable),
|
||||
cwd: this.cwd(),
|
||||
...this.params,
|
||||
});
|
||||
|
||||
const res: any = this.fromStore(this.storeConfig.store);
|
||||
|
||||
@ -12,7 +12,7 @@ describe("kubernetesClusterCategory", () => {
|
||||
let kubernetesClusterCategory: KubernetesClusterCategory;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
|
||||
});
|
||||
|
||||
@ -16,8 +16,7 @@ const navigateToHelmReleasesInjectable = getInjectable({
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
const route = di.inject(helmReleasesRouteInjectable);
|
||||
|
||||
return (parameters) =>
|
||||
navigateToRoute(route, { parameters });
|
||||
return (parameters) => navigateToRoute(route, { parameters });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -3,20 +3,23 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { ExecFileOptions } from "child_process";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
export type ExecFile = (filePath: string, args: string[]) => Promise<string>;
|
||||
export type ExecFile = (filePath: string, args: string[], options: ExecFileOptions) => Promise<string>;
|
||||
|
||||
const execFileInjectable = getInjectable({
|
||||
id: "exec-file",
|
||||
|
||||
instantiate: (): ExecFile => async (filePath, args) => {
|
||||
instantiate: (): ExecFile => {
|
||||
const asyncExecFile = promisify(execFile);
|
||||
|
||||
const result = await asyncExecFile(filePath, args);
|
||||
return async (filePath, args, options) => {
|
||||
const result = await asyncExecFile(filePath, args, options);
|
||||
|
||||
return result.stdout;
|
||||
return result.stdout;
|
||||
};
|
||||
},
|
||||
|
||||
causesSideEffects: true,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import assert from "assert";
|
||||
import type { PodContainer, PodContainerStatus } from "../endpoints";
|
||||
import type { Container, PodContainerStatus } from "../endpoints";
|
||||
import { Pod } from "../endpoints";
|
||||
|
||||
interface GetDummyPodOptions {
|
||||
@ -22,8 +22,8 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
initRunning = 0,
|
||||
} = rawOpts;
|
||||
|
||||
const containers: PodContainer[] = [];
|
||||
const initContainers: PodContainer[] = [];
|
||||
const containers: Container[] = [];
|
||||
const initContainers: Container[] = [];
|
||||
const containerStatuses: PodContainerStatus[] = [];
|
||||
const initContainerStatuses: PodContainerStatus[] = [];
|
||||
const pod = new Pod({
|
||||
@ -58,7 +58,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
|
||||
containers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
containerStatuses.push({
|
||||
@ -80,7 +80,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
|
||||
containers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
containerStatuses.push({
|
||||
@ -105,7 +105,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
|
||||
initContainers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
initContainerStatuses.push({
|
||||
@ -127,7 +127,7 @@ function getDummyPod(rawOpts: GetDummyPodOptions = {}): Pod {
|
||||
|
||||
initContainers.push({
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
});
|
||||
initContainerStatuses.push({
|
||||
@ -169,7 +169,7 @@ describe("Pods", () => {
|
||||
function getNamedContainer(name: string) {
|
||||
return {
|
||||
image: "dummy",
|
||||
imagePullPolicy: "dummy",
|
||||
imagePullPolicy: "Always",
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
@ -41,3 +41,4 @@ export * from "./service-account.api";
|
||||
export * from "./stateful-set.api";
|
||||
export * from "./storage-class.api";
|
||||
export * from "./legacy-globals";
|
||||
export * from "./types";
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import { metricsApi } from "./metrics.api";
|
||||
import type { PodContainer, PodMetricData, PodSpec } from "./pod.api";
|
||||
import type { PodMetricData, PodSpec } from "./pod.api";
|
||||
import type { Container } from "./types/container";
|
||||
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import { KubeObject } from "../kube-object";
|
||||
|
||||
@ -23,7 +24,7 @@ export interface JobSpec {
|
||||
};
|
||||
spec: PodSpec;
|
||||
};
|
||||
containers?: PodContainer[];
|
||||
containers?: Container[];
|
||||
restartPolicy?: string;
|
||||
terminationGracePeriodSeconds?: number;
|
||||
dnsPolicy?: string;
|
||||
|
||||
@ -8,11 +8,15 @@ import { metricsApi } from "./metrics.api";
|
||||
import type { DerivedKubeApiOptions, IgnoredKubeApiOptions, ResourceDescriptor } from "../kube-api";
|
||||
import { KubeApi } from "../kube-api";
|
||||
import type { RequireExactlyOne } from "type-fest";
|
||||
import type { KubeObjectMetadata, LocalObjectReference, Affinity, Toleration, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
|
||||
import type { KubeObjectMetadata, LocalObjectReference, Affinity, Toleration, NamespaceScopedMetadata } from "../kube-object";
|
||||
import type { SecretReference } from "./secret.api";
|
||||
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api";
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { isDefined } from "../../utils";
|
||||
import type { PodSecurityContext } from "./types/pod-security-context";
|
||||
import type { Probe } from "./types/probe";
|
||||
import type { Container } from "./types/container";
|
||||
import type { ObjectFieldSelector, ResourceFieldSelector } from "./types";
|
||||
|
||||
export class PodApi extends KubeApi<Pod> {
|
||||
constructor(opts: DerivedKubeApiOptions & IgnoredKubeApiOptions = {}) {
|
||||
@ -83,93 +87,6 @@ export enum PodStatusPhase {
|
||||
EVICTED = "Evicted",
|
||||
}
|
||||
|
||||
export interface ContainerPort {
|
||||
containerPort: number;
|
||||
hostIP?: string;
|
||||
hostPort?: number;
|
||||
name?: string;
|
||||
protocol?: "UDP" | "TCP" | "SCTP";
|
||||
}
|
||||
|
||||
export interface VolumeMount {
|
||||
name: string;
|
||||
readOnly?: boolean;
|
||||
mountPath: string;
|
||||
mountPropagation?: string;
|
||||
subPath?: string;
|
||||
subPathExpr?: string;
|
||||
}
|
||||
|
||||
export interface PodContainer extends Partial<Record<PodContainerProbe, IContainerProbe>> {
|
||||
name: string;
|
||||
image: string;
|
||||
command?: string[];
|
||||
args?: string[];
|
||||
ports?: ContainerPort[];
|
||||
resources?: {
|
||||
limits?: {
|
||||
cpu: string;
|
||||
memory: string;
|
||||
};
|
||||
requests?: {
|
||||
cpu: string;
|
||||
memory: string;
|
||||
};
|
||||
};
|
||||
terminationMessagePath?: string;
|
||||
terminationMessagePolicy?: string;
|
||||
env?: {
|
||||
name: string;
|
||||
value?: string;
|
||||
valueFrom?: {
|
||||
fieldRef?: {
|
||||
apiVersion: string;
|
||||
fieldPath: string;
|
||||
};
|
||||
secretKeyRef?: {
|
||||
key: string;
|
||||
name: string;
|
||||
};
|
||||
configMapKeyRef?: {
|
||||
key: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}[];
|
||||
envFrom?: {
|
||||
configMapRef?: LocalObjectReference;
|
||||
secretRef?: LocalObjectReference;
|
||||
}[];
|
||||
volumeMounts?: VolumeMount[];
|
||||
imagePullPolicy?: string;
|
||||
}
|
||||
|
||||
export type PodContainerProbe = "livenessProbe" | "readinessProbe" | "startupProbe";
|
||||
|
||||
interface IContainerProbe {
|
||||
httpGet?: {
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* either a port number or an IANA_SVC_NAME string referring to a port defined in the container
|
||||
*/
|
||||
port: number | string;
|
||||
scheme: string;
|
||||
host?: string;
|
||||
};
|
||||
exec?: {
|
||||
command: string[];
|
||||
};
|
||||
tcpSocket?: {
|
||||
port: number;
|
||||
};
|
||||
initialDelaySeconds?: number;
|
||||
timeoutSeconds?: number;
|
||||
periodSeconds?: number;
|
||||
successThreshold?: number;
|
||||
failureThreshold?: number;
|
||||
}
|
||||
|
||||
export interface ContainerStateRunning {
|
||||
startedAt: string;
|
||||
}
|
||||
@ -459,17 +376,6 @@ export interface ConfigMapProjection {
|
||||
optional?: boolean;
|
||||
}
|
||||
|
||||
export interface ObjectFieldSelector {
|
||||
fieldPath: string;
|
||||
apiVersion?: string;
|
||||
}
|
||||
|
||||
export interface ResourceFieldSelector {
|
||||
resource: string;
|
||||
containerName?: string;
|
||||
divisor?: string;
|
||||
}
|
||||
|
||||
export interface DownwardAPIVolumeFile {
|
||||
path: string;
|
||||
fieldRef?: ObjectFieldSelector;
|
||||
@ -674,43 +580,11 @@ export interface HostAlias {
|
||||
hostnames: string[];
|
||||
}
|
||||
|
||||
export interface SELinuxOptions {
|
||||
level?: string;
|
||||
role?: string;
|
||||
type?: string;
|
||||
user?: string;
|
||||
}
|
||||
|
||||
export interface SeccompProfile {
|
||||
localhostProfile?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface Sysctl {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface WindowsSecurityContextOptions {
|
||||
labelSelector?: LabelSelector;
|
||||
maxSkew: number;
|
||||
topologyKey: string;
|
||||
whenUnsatisfiable: string;
|
||||
}
|
||||
|
||||
export interface PodSecurityContext {
|
||||
fsGroup?: number;
|
||||
fsGroupChangePolicy?: string;
|
||||
runAsGroup?: number;
|
||||
runAsNonRoot?: boolean;
|
||||
runAsUser?: number;
|
||||
seLinuxOptions?: SELinuxOptions;
|
||||
seccompProfile?: SeccompProfile;
|
||||
supplementalGroups?: number[];
|
||||
sysctls?: Sysctl;
|
||||
windowsOptions?: WindowsSecurityContextOptions;
|
||||
}
|
||||
|
||||
export interface TopologySpreadConstraint {
|
||||
|
||||
}
|
||||
@ -719,7 +593,7 @@ export interface PodSpec {
|
||||
activeDeadlineSeconds?: number;
|
||||
affinity?: Affinity;
|
||||
automountServiceAccountToken?: boolean;
|
||||
containers?: PodContainer[];
|
||||
containers?: Container[];
|
||||
dnsPolicy?: string;
|
||||
enableServiceLinks?: boolean;
|
||||
ephemeralContainers?: unknown[];
|
||||
@ -729,7 +603,7 @@ export interface PodSpec {
|
||||
hostNetwork?: boolean;
|
||||
hostPID?: boolean;
|
||||
imagePullSecrets?: LocalObjectReference[];
|
||||
initContainers?: PodContainer[];
|
||||
initContainers?: Container[];
|
||||
nodeName?: string;
|
||||
nodeSelector?: Partial<Record<string, string>>;
|
||||
overhead?: Partial<Record<string, string>>;
|
||||
@ -931,44 +805,45 @@ export class Pod extends KubeObject<
|
||||
return this.getStatusPhase() !== "Running";
|
||||
}
|
||||
|
||||
getLivenessProbe(container: PodContainer) {
|
||||
return this.getProbe(container, "livenessProbe");
|
||||
getLivenessProbe(container: Container) {
|
||||
return this.getProbe(container, container.livenessProbe);
|
||||
}
|
||||
|
||||
getReadinessProbe(container: PodContainer) {
|
||||
return this.getProbe(container, "readinessProbe");
|
||||
getReadinessProbe(container: Container) {
|
||||
return this.getProbe(container, container.readinessProbe);
|
||||
}
|
||||
|
||||
getStartupProbe(container: PodContainer) {
|
||||
return this.getProbe(container, "startupProbe");
|
||||
getStartupProbe(container: Container) {
|
||||
return this.getProbe(container, container.startupProbe);
|
||||
}
|
||||
|
||||
private getProbe(container: PodContainer, field: PodContainerProbe): string[] {
|
||||
const probe: string[] = [];
|
||||
const probeData = container[field];
|
||||
private getProbe(container: Container, probe: Probe | undefined): string[] {
|
||||
const probeItems: string[] = [];
|
||||
|
||||
if (!probeData) {
|
||||
return probe;
|
||||
if (!probe) {
|
||||
return probeItems;
|
||||
}
|
||||
|
||||
const {
|
||||
httpGet, exec, tcpSocket,
|
||||
httpGet,
|
||||
exec,
|
||||
tcpSocket,
|
||||
initialDelaySeconds = 0,
|
||||
timeoutSeconds = 0,
|
||||
periodSeconds = 0,
|
||||
successThreshold = 0,
|
||||
failureThreshold = 0,
|
||||
} = probeData;
|
||||
} = probe;
|
||||
|
||||
// HTTP Request
|
||||
if (httpGet) {
|
||||
const { path = "", port, host = "", scheme } = httpGet;
|
||||
const { path = "", port, host = "", scheme = "HTTP" } = httpGet;
|
||||
const resolvedPort = typeof port === "number"
|
||||
? port
|
||||
// Try and find the port number associated witht the name or fallback to the name itself
|
||||
: container.ports?.find(containerPort => containerPort.name === port)?.containerPort || port;
|
||||
|
||||
probe.push(
|
||||
probeItems.push(
|
||||
"http-get",
|
||||
`${scheme.toLowerCase()}://${host}:${resolvedPort}${path}`,
|
||||
);
|
||||
@ -976,15 +851,15 @@ export class Pod extends KubeObject<
|
||||
|
||||
// Command
|
||||
if (exec?.command) {
|
||||
probe.push(`exec [${exec.command.join(" ")}]`);
|
||||
probeItems.push(`exec [${exec.command.join(" ")}]`);
|
||||
}
|
||||
|
||||
// TCP Probe
|
||||
if (tcpSocket?.port) {
|
||||
probe.push(`tcp-socket :${tcpSocket.port}`);
|
||||
probeItems.push(`tcp-socket :${tcpSocket.port}`);
|
||||
}
|
||||
|
||||
probe.push(
|
||||
probeItems.push(
|
||||
`delay=${initialDelaySeconds}s`,
|
||||
`timeout=${timeoutSeconds}s`,
|
||||
`period=${periodSeconds}s`,
|
||||
@ -992,7 +867,7 @@ export class Pod extends KubeObject<
|
||||
`#failure=${failureThreshold}`,
|
||||
);
|
||||
|
||||
return probe;
|
||||
return probeItems;
|
||||
}
|
||||
|
||||
getNodeName(): string | undefined {
|
||||
|
||||
19
src/common/k8s-api/endpoints/types/capabilities.ts
Normal file
19
src/common/k8s-api/endpoints/types/capabilities.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds and removes POSIX capabilities from running containers.
|
||||
*/
|
||||
export interface Capabilities {
|
||||
/**
|
||||
* Added capabilities
|
||||
*/
|
||||
add?: string[];
|
||||
|
||||
/**
|
||||
* Removed capabilities
|
||||
*/
|
||||
drop?: string[];
|
||||
}
|
||||
12
src/common/k8s-api/endpoints/types/container-port.ts
Normal file
12
src/common/k8s-api/endpoints/types/container-port.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface ContainerPort {
|
||||
containerPort: number;
|
||||
hostIP?: string;
|
||||
hostPort?: number;
|
||||
name?: string;
|
||||
protocol?: "UDP" | "TCP" | "SCTP";
|
||||
}
|
||||
176
src/common/k8s-api/endpoints/types/container.ts
Normal file
176
src/common/k8s-api/endpoints/types/container.ts
Normal file
@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { Lifecycle } from "./lifecycle";
|
||||
import type { ResourceRequirements } from "./resource-requirements";
|
||||
import type { SecurityContext } from "./security-context";
|
||||
import type { Probe } from "./probe";
|
||||
import type { VolumeDevice } from "./volume-device";
|
||||
import type { VolumeMount } from "./volume-mount";
|
||||
import type { ContainerPort } from "./container-port";
|
||||
import type { EnvFromSource } from "./env-from-source";
|
||||
import type { EnvVar } from "./env-var";
|
||||
|
||||
/**
|
||||
* A single application container that you want to run within a pod.
|
||||
*/
|
||||
export interface Container {
|
||||
/**
|
||||
* Arguments to the entrypoint. The docker image's CMD is used if this is not provided. Variable
|
||||
* references `$(VAR_NAME)` are expanded using the container's environment.
|
||||
*
|
||||
* If a variable cannot be resolved, the reference in the input string will be unchanged.
|
||||
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
|
||||
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
|
||||
*
|
||||
* Escaped references will never be expanded, regardless of whether the variable exists or not.
|
||||
* Cannot be updated.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
|
||||
*/
|
||||
args?: string[];
|
||||
|
||||
/**
|
||||
* Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this
|
||||
* is not provided. Variable references `$(VAR_NAME)` are expanded using the container's
|
||||
* environment.
|
||||
*
|
||||
* If a variable cannot be resolved, the reference in the input string will be unchanged.
|
||||
* Double `$$` are reduced to a single `$`, which allows for escaping the `$(VAR_NAME)` syntax:
|
||||
* i.e. `"$$(VAR_NAME)"` will produce the string literal `"$(VAR_NAME)`".
|
||||
*
|
||||
* Escaped references will never be expanded, regardless of whether the variable exists or not.
|
||||
* Cannot be updated.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell
|
||||
*/
|
||||
command?: string[];
|
||||
|
||||
/**
|
||||
* List of environment variables to set in the container. Cannot be updated.
|
||||
*/
|
||||
env?: EnvVar[];
|
||||
|
||||
/**
|
||||
* List of sources to populate environment variables in the container. The keys defined within a
|
||||
* source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the
|
||||
* container is starting.
|
||||
*
|
||||
* When a key exists in multiple sources, the value associated with the last source will take
|
||||
* precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be
|
||||
* updated.
|
||||
*/
|
||||
envFrom?: EnvFromSource[];
|
||||
|
||||
/**
|
||||
* Docker image name.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/concepts/containers/images
|
||||
*/
|
||||
image?: string;
|
||||
|
||||
/**
|
||||
* Image pull policy. Defaults to `"Always"` if :latest tag is specified, or `"IfNotPresent"`
|
||||
* otherwise. Cannot be updated.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/concepts/containers/images#updating-images
|
||||
*/
|
||||
imagePullPolicy?: "Always" | "Never" | "IfNotPresent";
|
||||
|
||||
lifecycle?: Lifecycle;
|
||||
livenessProbe?: Probe;
|
||||
|
||||
/**
|
||||
* Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique
|
||||
* name. Cannot be updated.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* List of ports to expose from the container. Exposing a port here gives the system additional
|
||||
* information about the network connections a container uses, but is primarily informational.
|
||||
* Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is
|
||||
* listening on the default `"0.0.0.0"` address inside a container will be accessible from the
|
||||
* network. Cannot be updated.
|
||||
*/
|
||||
ports?: ContainerPort[];
|
||||
|
||||
readinessProbe?: Probe;
|
||||
resources?: ResourceRequirements;
|
||||
securityContext?: SecurityContext;
|
||||
startupProbe?: Probe;
|
||||
|
||||
/**
|
||||
* Whether this container should allocate a buffer for stdin in the container runtime. If this is
|
||||
* not set, reads from stdin in the container will always result in EOF.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
stdin?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the container runtime should close the stdin channel after it has been opened by a
|
||||
* single attach. When stdin is true the stdin stream will remain open across multiple attach
|
||||
* sessions.
|
||||
*
|
||||
* If stdinOnce is set to true, stdin is opened on container start, is empty until the first
|
||||
* client attaches to stdin, and then remains open and accepts data until the client disconnects,
|
||||
* at which time stdin is closed and remains closed until the container is restarted.
|
||||
*
|
||||
* If this flag is false, a container processes that reads from stdin will never receive an EOF.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
stdinOnce?: boolean;
|
||||
|
||||
/**
|
||||
* Path at which the file to which the container's termination message will be written
|
||||
* is mounted into the container's filesystem. Message written is intended to be brief final
|
||||
* status, such as an assertion failure message.
|
||||
*
|
||||
* Will be truncated by the node if greater than 4096 bytes.
|
||||
* The total message length across all containers will be limited to 12kb. Cannot be updated.
|
||||
*
|
||||
* @default "/dev/termination-log"
|
||||
*/
|
||||
terminationMessagePath?: string;
|
||||
|
||||
/**
|
||||
* Indicate how the termination message should be populated.
|
||||
*
|
||||
* - `File`: will use the contents of {@link terminationMessagePath} to populate the container
|
||||
* status message on both success and failure.
|
||||
*
|
||||
* - `FallbackToLogsOnError`: will use the last chunk of container log output if the
|
||||
* termination message file is empty and the container exited with an error.
|
||||
*
|
||||
* The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Cannot be updated.
|
||||
*
|
||||
* @default "File"
|
||||
*/
|
||||
terminationMessagePolicy?: "File" | "FallbackToLogsOnError";
|
||||
|
||||
/**
|
||||
* Whether this container should allocate a TTY for itself, also requires 'stdin' to be true.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
tty?: boolean;
|
||||
|
||||
/**
|
||||
* volumeDevices is the list of block devices to be used by the container.
|
||||
*/
|
||||
volumeDevices?: VolumeDevice[];
|
||||
|
||||
/**
|
||||
* Pod volumes to mount into the container's filesystem. Cannot be updated.
|
||||
*/
|
||||
volumeMounts?: VolumeMount[];
|
||||
|
||||
/**
|
||||
* Container's working directory. If not specified, the container runtime's default will be used,
|
||||
* which might be configured in the container image. Cannot be updated.
|
||||
*/
|
||||
workingDir?: string;
|
||||
}
|
||||
14
src/common/k8s-api/endpoints/types/env-from-source.ts
Normal file
14
src/common/k8s-api/endpoints/types/env-from-source.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { EnvSource } from "./env-source";
|
||||
|
||||
export interface EnvFromSource {
|
||||
configMapRef?: EnvSource;
|
||||
/**
|
||||
* An identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER.
|
||||
*/
|
||||
prefix?: string;
|
||||
secretRef?: EnvSource;
|
||||
}
|
||||
13
src/common/k8s-api/endpoints/types/env-source.ts
Normal file
13
src/common/k8s-api/endpoints/types/env-source.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { LocalObjectReference } from "../../kube-object";
|
||||
|
||||
export interface EnvSource extends LocalObjectReference {
|
||||
/**
|
||||
* Whether the object must be defined
|
||||
*/
|
||||
optional?: boolean;
|
||||
}
|
||||
10
src/common/k8s-api/endpoints/types/env-var-key-selector.ts
Normal file
10
src/common/k8s-api/endpoints/types/env-var-key-selector.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface EnvVarKeySelector {
|
||||
key: string;
|
||||
name?: string;
|
||||
optional?: boolean;
|
||||
}
|
||||
15
src/common/k8s-api/endpoints/types/env-var-source.ts
Normal file
15
src/common/k8s-api/endpoints/types/env-var-source.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { EnvVarKeySelector } from "./env-var-key-selector";
|
||||
import type { ObjectFieldSelector } from "./object-field-selector";
|
||||
import type { ResourceFieldSelector } from "./resource-field-selector";
|
||||
|
||||
export interface EnvVarSource {
|
||||
configMapKeyRef?: EnvVarKeySelector;
|
||||
fieldRef?: ObjectFieldSelector;
|
||||
resourceFieldRef?: ResourceFieldSelector;
|
||||
secretKeyRef?: EnvVarKeySelector;
|
||||
}
|
||||
12
src/common/k8s-api/endpoints/types/env-var.ts
Normal file
12
src/common/k8s-api/endpoints/types/env-var.ts
Normal 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 type { EnvVarSource } from "./env-var-source";
|
||||
|
||||
export interface EnvVar {
|
||||
name: string;
|
||||
value?: string;
|
||||
valueFrom?: EnvVarSource;
|
||||
}
|
||||
19
src/common/k8s-api/endpoints/types/exec-action.ts
Normal file
19
src/common/k8s-api/endpoints/types/exec-action.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ExecAction describes a "run in container" action.
|
||||
*/
|
||||
export interface ExecAction {
|
||||
/**
|
||||
* Command is the command line to execute inside the container, the working directory for the
|
||||
* command is root ('\\') in the container's filesystem. The command is simply exec'd, it is not
|
||||
* run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell,
|
||||
* you need to explicitly call out to that shell.
|
||||
*
|
||||
* Exit status of 0 is treated as live/healthy and non-zero is unhealthy.
|
||||
*/
|
||||
command?: string[];
|
||||
}
|
||||
17
src/common/k8s-api/endpoints/types/handler.ts
Normal file
17
src/common/k8s-api/endpoints/types/handler.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ExecAction } from "./exec-action";
|
||||
import type { HttpGetAction } from "./http-get-action";
|
||||
import type { TcpSocketAction } from "./tcp-socket-action";
|
||||
|
||||
/**
|
||||
* Handler defines a specific action that should be taken.
|
||||
*/
|
||||
export interface Handler {
|
||||
exec?: ExecAction;
|
||||
httpGet?: HttpGetAction;
|
||||
tcpSocket?: TcpSocketAction;
|
||||
}
|
||||
38
src/common/k8s-api/endpoints/types/http-get-action.ts
Normal file
38
src/common/k8s-api/endpoints/types/http-get-action.ts
Normal 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 type { HttpHeader } from "./http-header";
|
||||
|
||||
/**
|
||||
* An action based on HTTP Get requests.
|
||||
*/
|
||||
export interface HttpGetAction {
|
||||
/**
|
||||
* Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Custom headers to set in the request. HTTP allows repeated headers.
|
||||
*/
|
||||
httpHeaders?: HttpHeader[];
|
||||
|
||||
/**
|
||||
* Path to access on the HTTP server.
|
||||
*/
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* The PORT to request from.
|
||||
*/
|
||||
port: string | number;
|
||||
|
||||
/**
|
||||
* Scheme to use for connecting to the host.
|
||||
*
|
||||
* @default "HTTP"
|
||||
*/
|
||||
scheme?: string;
|
||||
}
|
||||
19
src/common/k8s-api/endpoints/types/http-header.ts
Normal file
19
src/common/k8s-api/endpoints/types/http-header.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A custom header to be used in HTTP probes and get actions
|
||||
*/
|
||||
export interface HttpHeader {
|
||||
/**
|
||||
* Field name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The value of the field
|
||||
*/
|
||||
value: string;
|
||||
}
|
||||
36
src/common/k8s-api/endpoints/types/index.ts
Normal file
36
src/common/k8s-api/endpoints/types/index.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export * from "./aggregation-rule";
|
||||
export * from "./capabilities";
|
||||
export * from "./container";
|
||||
export * from "./container-port";
|
||||
export * from "./env-from-source";
|
||||
export * from "./env-source";
|
||||
export * from "./env-var-key-selector";
|
||||
export * from "./env-var-source";
|
||||
export * from "./env-var";
|
||||
export * from "./exec-action";
|
||||
export * from "./handler";
|
||||
export * from "./http-get-action";
|
||||
export * from "./http-header";
|
||||
export * from "./job-template-spec";
|
||||
export * from "./lifecycle";
|
||||
export * from "./object-field-selector";
|
||||
export * from "./persistent-volume-claim-template-spec";
|
||||
export * from "./pod-security-context";
|
||||
export * from "./pod-template-spec";
|
||||
export * from "./policy-rule";
|
||||
export * from "./probe";
|
||||
export * from "./resource-field-selector";
|
||||
export * from "./resource-requirements";
|
||||
export * from "./role-ref";
|
||||
export * from "./se-linux-options";
|
||||
export * from "./seccomp-profile";
|
||||
export * from "./subject";
|
||||
export * from "./tcp-socket-action";
|
||||
export * from "./volume-device";
|
||||
export * from "./volume-mount";
|
||||
export * from "./windows-security-context-options";
|
||||
17
src/common/k8s-api/endpoints/types/lifecycle.ts
Normal file
17
src/common/k8s-api/endpoints/types/lifecycle.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Handler } from "./handler";
|
||||
|
||||
/**
|
||||
* Lifecycle describes actions that the management system should take in response to container
|
||||
* lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container
|
||||
* blocks until the action is complete, unless the container process fails, in which case the
|
||||
* handler is aborted.
|
||||
*/
|
||||
export interface Lifecycle {
|
||||
postStart?: Handler;
|
||||
preStop?: Handler;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface ObjectFieldSelector {
|
||||
apiVersion?: string;
|
||||
fieldPath: string;
|
||||
}
|
||||
22
src/common/k8s-api/endpoints/types/pod-security-context.ts
Normal file
22
src/common/k8s-api/endpoints/types/pod-security-context.ts
Normal 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 type { SeLinuxOptions } from "./se-linux-options";
|
||||
import type { SeccompProfile } from "./seccomp-profile";
|
||||
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
|
||||
import type { Sysctl } from "../pod.api";
|
||||
|
||||
|
||||
export interface PodSecurityContext {
|
||||
fsGroup?: number;
|
||||
fsGroupChangePolicy?: string;
|
||||
runAsGroup?: number;
|
||||
runAsNonRoot?: boolean;
|
||||
runAsUser?: number;
|
||||
seLinuxOptions?: SeLinuxOptions;
|
||||
seccompProfile?: SeccompProfile;
|
||||
supplementalGroups?: number[];
|
||||
sysctls?: Sysctl;
|
||||
windowsOptions?: WindowsSecurityContextOptions;
|
||||
}
|
||||
80
src/common/k8s-api/endpoints/types/probe.ts
Normal file
80
src/common/k8s-api/endpoints/types/probe.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ExecAction } from "./exec-action";
|
||||
import type { HttpGetAction } from "./http-get-action";
|
||||
import type { TcpSocketAction } from "./tcp-socket-action";
|
||||
|
||||
/**
|
||||
* Describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.
|
||||
*/
|
||||
export interface Probe {
|
||||
exec?: ExecAction;
|
||||
|
||||
/**
|
||||
* Minimum consecutive failures for the probe to be considered failed after having succeeded.
|
||||
*
|
||||
* @default 3
|
||||
* @minimum 1
|
||||
*/
|
||||
failureThreshold?: number;
|
||||
|
||||
httpGet?: HttpGetAction;
|
||||
|
||||
/**
|
||||
* Duration after the container has started before liveness probes are initiated.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
|
||||
*/
|
||||
initialDelaySeconds?: number;
|
||||
|
||||
/**
|
||||
* How often to perform the probe.
|
||||
*
|
||||
* @default 10
|
||||
* @minimum 1
|
||||
*/
|
||||
periodSeconds?: number;
|
||||
|
||||
/**
|
||||
* Minimum consecutive successes for the probe to be considered successful after having failed.
|
||||
*
|
||||
* Must be 1 for liveness and startup.
|
||||
*
|
||||
* @default 1
|
||||
* @minimum 1
|
||||
*/
|
||||
successThreshold?: number;
|
||||
|
||||
tcpSocket?: TcpSocketAction;
|
||||
|
||||
/**
|
||||
* Duration the pod needs to terminate gracefully upon probe failure.
|
||||
*
|
||||
* The grace period is the duration in seconds after the processes running in the pod are sent a
|
||||
* termination signal and the time when the processes are forcibly halted with a kill signal.
|
||||
*
|
||||
* Set this value longer than the expected cleanup time for your process.
|
||||
*
|
||||
* If this value is not set, the pod's terminationGracePeriodSeconds will be used. Otherwise,
|
||||
* this value overrides the value provided by the pod spec. Value must be non-negative integer.
|
||||
* The value zero indicates stop immediately via the kill signal (no opportunity to shut down).
|
||||
*
|
||||
* This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.
|
||||
*
|
||||
* @minimum 1
|
||||
*/
|
||||
terminationGracePeriodSeconds?: number;
|
||||
|
||||
/**
|
||||
* Duration after which the probe times out.
|
||||
*
|
||||
* More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
|
||||
*
|
||||
* @default 1
|
||||
* @minimum 1
|
||||
*/
|
||||
timeoutSeconds?: number;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface ResourceFieldSelector {
|
||||
containerName?: string;
|
||||
divisor?: string;
|
||||
resource: string;
|
||||
}
|
||||
29
src/common/k8s-api/endpoints/types/se-linux-options.ts
Normal file
29
src/common/k8s-api/endpoints/types/se-linux-options.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SELinuxOptions are the labels to be applied to the container
|
||||
*/
|
||||
export interface SeLinuxOptions {
|
||||
/**
|
||||
* The SELinux `level` label that applies to the container.
|
||||
*/
|
||||
level?: string;
|
||||
|
||||
/**
|
||||
* The SELinux `role` label that applies to the container.
|
||||
*/
|
||||
role?: string;
|
||||
|
||||
/**
|
||||
* The SELinux `type` label that applies to the container.
|
||||
*/
|
||||
type?: string;
|
||||
|
||||
/**
|
||||
* The SELinux `user` label that applies to the container.
|
||||
*/
|
||||
user?: string;
|
||||
}
|
||||
29
src/common/k8s-api/endpoints/types/seccomp-profile.ts
Normal file
29
src/common/k8s-api/endpoints/types/seccomp-profile.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a pod's or a container's seccomp profile settings. Only one profile source may be set.
|
||||
*/
|
||||
export interface SeccompProfile {
|
||||
/**
|
||||
* Indicates a profile defined in a file on the node should be used. The profile must be
|
||||
* preconfigured on the node to work. Must be a descending path, relative to the kubelet's
|
||||
* configured seccomp profile location. Must only be set if type is "Localhost".
|
||||
*/
|
||||
localhostProfile?: string;
|
||||
|
||||
/**
|
||||
* Indicates which kind of seccomp profile will be applied.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* | Value | Description |
|
||||
* |--|--|
|
||||
* | `Localhost` | A profile defined in a file on the node should be used. |
|
||||
* | `RuntimeDefault` | The container runtime default profile should be used. |
|
||||
* | `Unconfined` | No profile should be applied. |
|
||||
*/
|
||||
type: "Localhost" | "RuntimeDefault" | "Unconfined";
|
||||
}
|
||||
55
src/common/k8s-api/endpoints/types/security-context.ts
Normal file
55
src/common/k8s-api/endpoints/types/security-context.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { Capabilities } from "./capabilities";
|
||||
import type { SeLinuxOptions } from "./se-linux-options";
|
||||
import type { SeccompProfile } from "./seccomp-profile";
|
||||
import type { WindowsSecurityContextOptions } from "./windows-security-context-options";
|
||||
|
||||
/**
|
||||
* SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.
|
||||
*/
|
||||
export interface SecurityContext {
|
||||
/**
|
||||
* AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN
|
||||
*/
|
||||
allowPrivilegeEscalation?: boolean;
|
||||
|
||||
capabilities?: Capabilities;
|
||||
|
||||
/**
|
||||
* Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false.
|
||||
*/
|
||||
privileged?: boolean;
|
||||
|
||||
/**
|
||||
* procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled.
|
||||
*/
|
||||
procMount?: string;
|
||||
|
||||
/**
|
||||
* Whether this container has a read-only root filesystem. Default is false.
|
||||
*/
|
||||
readOnlyRootFilesystem?: boolean;
|
||||
|
||||
/**
|
||||
* The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
*/
|
||||
runAsGroup?: number;
|
||||
|
||||
/**
|
||||
* Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
*/
|
||||
runAsNonRoot?: boolean;
|
||||
|
||||
/**
|
||||
* The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
*/
|
||||
runAsUser?: number;
|
||||
|
||||
seLinuxOptions?: SeLinuxOptions;
|
||||
seccompProfile?: SeccompProfile;
|
||||
windowsOptions?: WindowsSecurityContextOptions;
|
||||
}
|
||||
19
src/common/k8s-api/endpoints/types/tcp-socket-action.ts
Normal file
19
src/common/k8s-api/endpoints/types/tcp-socket-action.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An action based on opening a socket
|
||||
*/
|
||||
export interface TcpSocketAction {
|
||||
/**
|
||||
* Host name to connect to, defaults to the pod IP.
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Port to connect to
|
||||
*/
|
||||
port: number | string;
|
||||
}
|
||||
19
src/common/k8s-api/endpoints/types/volume-device.ts
Normal file
19
src/common/k8s-api/endpoints/types/volume-device.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A mapping of a raw block device within a container.
|
||||
*/
|
||||
export interface VolumeDevice {
|
||||
/**
|
||||
* The path inside of the container that the device will be mapped to.
|
||||
*/
|
||||
devicePath: string;
|
||||
|
||||
/**
|
||||
* Must match the name of a persistentVolumeClaim in the pod
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
13
src/common/k8s-api/endpoints/types/volume-mount.ts
Normal file
13
src/common/k8s-api/endpoints/types/volume-mount.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
export interface VolumeMount {
|
||||
name: string;
|
||||
readOnly?: boolean;
|
||||
mountPath: string;
|
||||
mountPropagation?: string;
|
||||
subPath?: string;
|
||||
subPathExpr?: string;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Windows-specific options and credentials.
|
||||
*/
|
||||
export interface WindowsSecurityContextOptions {
|
||||
/**
|
||||
* The location of the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)
|
||||
* inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.
|
||||
*/
|
||||
gmsaCredentialSpec?: string;
|
||||
|
||||
/**
|
||||
* The name of the GMSA credential spec to use.
|
||||
*/
|
||||
gmsaCredentialSpecName?: string;
|
||||
|
||||
/**
|
||||
* Determines if a container should be run as a 'Host Process' container.
|
||||
*
|
||||
* This field is alpha-level and will only be honored by components that enable the
|
||||
* WindowsHostProcessContainers feature flag.
|
||||
*
|
||||
* Setting this field without the feature flag will result in errors when validating the Pod.
|
||||
*
|
||||
* All of a Pod's containers must have the same effective HostProcess value (it is not allowed to
|
||||
* have a mix of HostProcess containers and non-HostProcess containers).
|
||||
*
|
||||
* In addition, if HostProcess is true then HostNetwork must also be set to true.
|
||||
*/
|
||||
hostProcess?: boolean;
|
||||
|
||||
/**
|
||||
* The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.
|
||||
*/
|
||||
runAsUserName?: string;
|
||||
}
|
||||
@ -18,6 +18,7 @@ import { requestChannelListenerInjectionToken } from "./request-channel-listener
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import { getPromiseStatus } from "../../test-utils/get-promise-status";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
type TestMessageChannel = MessageChannel<string>;
|
||||
type TestRequestChannel = RequestChannel<string, string>;
|
||||
@ -47,12 +48,16 @@ describe("channel", () => {
|
||||
});
|
||||
|
||||
builder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.register(testMessageChannelInjectable);
|
||||
runInAction(() => {
|
||||
mainDi.register(testMessageChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testChannelListenerInTestWindowInjectable);
|
||||
windowDi.register(testMessageChannelInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testChannelListenerInTestWindowInjectable);
|
||||
windowDi.register(testMessageChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
mainDi = builder.mainDi;
|
||||
@ -126,12 +131,16 @@ describe("channel", () => {
|
||||
});
|
||||
|
||||
applicationBuilder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.register(testChannelListenerInMainInjectable);
|
||||
mainDi.register(testMessageChannelInjectable);
|
||||
runInAction(() => {
|
||||
mainDi.register(testChannelListenerInMainInjectable);
|
||||
mainDi.register(testMessageChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
applicationBuilder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testMessageChannelInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testMessageChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
await applicationBuilder.render();
|
||||
@ -172,12 +181,16 @@ describe("channel", () => {
|
||||
});
|
||||
|
||||
applicationBuilder.beforeApplicationStart((mainDi) => {
|
||||
mainDi.register(testChannelListenerInMainInjectable);
|
||||
mainDi.register(testRequestChannelInjectable);
|
||||
runInAction(() => {
|
||||
mainDi.register(testChannelListenerInMainInjectable);
|
||||
mainDi.register(testRequestChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
applicationBuilder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testRequestChannelInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRequestChannelInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
await applicationBuilder.render();
|
||||
|
||||
@ -18,11 +18,15 @@ describe("sync-box", () => {
|
||||
applicationBuilder = getApplicationBuilder();
|
||||
|
||||
applicationBuilder.beforeApplicationStart(mainDi => {
|
||||
mainDi.register(someInjectable);
|
||||
runInAction(() => {
|
||||
mainDi.register(someInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
applicationBuilder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(someInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(someInjectable);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ describe("with-error-logging", () => {
|
||||
let decorated: (a: string, b: string) => number | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
loggerStub = {
|
||||
error: jest.fn(),
|
||||
@ -119,7 +119,7 @@ describe("with-error-logging", () => {
|
||||
let toBeDecorated: AsyncFnMock<typeof decorated>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting();
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
loggerStub = {
|
||||
error: jest.fn(),
|
||||
|
||||
@ -47,7 +47,7 @@ export {
|
||||
} from "../../common/k8s-api/kube-object.store";
|
||||
|
||||
export {
|
||||
type PodContainer as IPodContainer,
|
||||
type Container as IPodContainer,
|
||||
type PodContainerStatus as IPodContainerStatus,
|
||||
Pod,
|
||||
PodApi as PodsApi,
|
||||
|
||||
@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { ExtensionDiscovery } from "./extension-discovery";
|
||||
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
|
||||
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
|
||||
import isCompatibleBundledExtensionInjectable from "./is-compatible-bundled-extension/is-compatible-bundled-extension.injectable";
|
||||
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
|
||||
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
|
||||
import installExtensionInjectable from "../extension-installer/install-extension/install-extension.injectable";
|
||||
@ -25,24 +24,11 @@ const extensionDiscoveryInjectable = getInjectable({
|
||||
new ExtensionDiscovery({
|
||||
extensionLoader: di.inject(extensionLoaderInjectable),
|
||||
extensionsStore: di.inject(extensionsStoreInjectable),
|
||||
|
||||
extensionInstallationStateStore: di.inject(
|
||||
extensionInstallationStateStoreInjectable,
|
||||
),
|
||||
|
||||
isCompatibleBundledExtension: di.inject(
|
||||
isCompatibleBundledExtensionInjectable,
|
||||
),
|
||||
|
||||
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
|
||||
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
|
||||
|
||||
installExtension: di.inject(installExtensionInjectable),
|
||||
installExtensions: di.inject(installExtensionsInjectable),
|
||||
|
||||
extensionPackageRootDirectory: di.inject(
|
||||
extensionPackageRootDirectoryInjectable,
|
||||
),
|
||||
|
||||
extensionPackageRootDirectory: di.inject(extensionPackageRootDirectoryInjectable),
|
||||
staticFilesDirectory: di.inject(staticFilesDirectoryInjectable),
|
||||
readJsonFile: di.inject(readJsonFileInjectable),
|
||||
pathExists: di.inject(pathExistsInjectable),
|
||||
|
||||
@ -27,12 +27,8 @@ import type { Watch } from "../../common/fs/watch/watch.injectable";
|
||||
interface Dependencies {
|
||||
extensionLoader: ExtensionLoader;
|
||||
extensionsStore: ExtensionsStore;
|
||||
|
||||
extensionInstallationStateStore: ExtensionInstallationStateStore;
|
||||
|
||||
isCompatibleBundledExtension: (manifest: LensExtensionManifest) => boolean;
|
||||
isCompatibleExtension: (manifest: LensExtensionManifest) => boolean;
|
||||
|
||||
installExtension: (name: string) => Promise<void>;
|
||||
installExtensions: (packageJsonPath: string, packagesJson: PackageJson) => Promise<void>;
|
||||
extensionPackageRootDirectory: string;
|
||||
@ -102,7 +98,7 @@ export class ExtensionDiscovery {
|
||||
|
||||
public events = new EventEmitter();
|
||||
|
||||
constructor(protected dependencies : Dependencies) {
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
makeObservable(this);
|
||||
}
|
||||
|
||||
@ -369,7 +365,7 @@ export class ExtensionDiscovery {
|
||||
const extensionDir = path.dirname(manifestPath);
|
||||
const npmPackage = path.join(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
|
||||
const absolutePath = (isProduction && await this.dependencies.pathExists(npmPackage)) ? npmPackage : extensionDir;
|
||||
const isCompatible = (isBundled && this.dependencies.isCompatibleBundledExtension(manifest)) || this.dependencies.isCompatibleExtension(manifest);
|
||||
const isCompatible = isBundled || this.dependencies.isCompatibleExtension(manifest);
|
||||
|
||||
return {
|
||||
id,
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import appSemanticVersionInjectable from "../../../common/vars/app-semantic-version.injectable";
|
||||
import { isCompatibleBundledExtension } from "./is-compatible-bundled-extension";
|
||||
|
||||
const isCompatibleBundledExtensionInjectable = getInjectable({
|
||||
id: "is-compatible-bundled-extension",
|
||||
instantiate: (di) => isCompatibleBundledExtension({
|
||||
appSemVer: di.inject(appSemanticVersionInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
export default isCompatibleBundledExtensionInjectable;
|
||||
@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { LensExtensionManifest } from "../../lens-extension";
|
||||
import { isProduction } from "../../../common/vars";
|
||||
import type { SemVer } from "semver";
|
||||
|
||||
interface Dependencies {
|
||||
appSemVer: SemVer;
|
||||
}
|
||||
|
||||
export const isCompatibleBundledExtension =
|
||||
({ appSemVer }: Dependencies) =>
|
||||
(manifest: LensExtensionManifest): boolean =>
|
||||
!isProduction || manifest.version === appSemVer.raw;
|
||||
@ -25,7 +25,7 @@ export class ExtensionInstaller {
|
||||
constructor(private dependencies: Dependencies) {}
|
||||
|
||||
get npmPath() {
|
||||
return __non_webpack_require__.resolve("npm/bin/npm-cli");
|
||||
return __non_webpack_require__.resolve("npm");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,7 +58,7 @@ export class ExtensionInstaller {
|
||||
|
||||
try {
|
||||
logger.info(`${logModule} installing package from ${name} to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
await this.npm(["install", "--no-audit", "--only=prod", "--prefer-offline", "--no-package-lock", "--no-save", name]);
|
||||
await this.npm(["install", "--no-audit", "--only=prod", "--package-lock=false", "--prefer-offline", "--no-package-lock", name]);
|
||||
logger.info(`${logModule} package ${name} installed to ${this.dependencies.extensionPackageRootDirectory}`);
|
||||
} finally {
|
||||
this.installLock.release();
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||
import { runInAction } from "mobx";
|
||||
import type { LensExtension } from "../../lens-extension";
|
||||
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
||||
|
||||
@ -27,17 +28,23 @@ const extensionInjectable = getInjectable({
|
||||
getInjectablesOfExtension(instance),
|
||||
);
|
||||
|
||||
childDi.register(...injectables);
|
||||
runInAction(() => {
|
||||
childDi.register(...injectables);
|
||||
});
|
||||
},
|
||||
|
||||
deregister: () => {
|
||||
parentDi.deregister(extensionInjectable);
|
||||
runInAction(() => {
|
||||
parentDi.deregister(extensionInjectable);
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
parentDi.register(extensionInjectable);
|
||||
runInAction(() => {
|
||||
parentDi.register(extensionInjectable);
|
||||
});
|
||||
|
||||
return parentDi.inject(extensionInjectable);
|
||||
},
|
||||
|
||||
@ -15,6 +15,8 @@ export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||
loadExtension(extension: LensExtension) {
|
||||
this.extension = extension;
|
||||
|
||||
this.params.projectVersion ??= this.extension.version;
|
||||
|
||||
return super.load();
|
||||
}
|
||||
|
||||
|
||||
1311
src/extensions/npm/extensions/package-lock.json
generated
1311
src/extensions/npm/extensions/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ import quitAndInstallUpdateInjectable from "../../main/application-update/quit-a
|
||||
import appVersionInjectable from "../../common/vars/app-version.injectable";
|
||||
import periodicalCheckForUpdatesInjectable from "../../main/application-update/periodical-check-for-updates/periodical-check-for-updates.injectable";
|
||||
import { advanceFakeTime, useFakeTime } from "../../common/test-utils/use-fake-time";
|
||||
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
||||
|
||||
describe("analytics for installing update", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@ -51,6 +52,8 @@ describe("analytics for installing update", () => {
|
||||
|
||||
mainDi.override(publishIsConfiguredInjectable, () => true);
|
||||
|
||||
mainDi.unoverride(emitEventInjectable);
|
||||
|
||||
const eventBus = mainDi.inject(appEventBusInjectable);
|
||||
|
||||
eventBus.addListener(analyticsListenerMock);
|
||||
@ -65,7 +68,6 @@ describe("analytics for installing update", () => {
|
||||
mainDi.permitSideEffects(periodicalCheckForUpdatesInjectable);
|
||||
|
||||
await builder.render();
|
||||
|
||||
});
|
||||
|
||||
it("sends event to analytics for being checked periodically", () => {
|
||||
|
||||
@ -10,7 +10,7 @@ import type { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../../../common/front-end-routing/front-end-route-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import React from "react";
|
||||
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
|
||||
@ -45,7 +45,9 @@ describe("disable kube object detail items when cluster is not relevant", () =>
|
||||
|
||||
windowDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);
|
||||
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
isEnabledForClusterMock = asyncFn();
|
||||
|
||||
@ -39,7 +39,9 @@ describe("reactively hide kube object detail item", () => {
|
||||
} as unknown as ApiManager),
|
||||
);
|
||||
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
someObservable = observable.box(false);
|
||||
|
||||
@ -10,7 +10,7 @@ import type { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../../../common/front-end-routing/front-end-route-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import React from "react";
|
||||
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
|
||||
@ -32,7 +32,10 @@ describe("disable kube object menu items when cluster is not relevant", () => {
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
isEnabledForClusterMock = asyncFn();
|
||||
|
||||
@ -26,7 +26,9 @@ describe("reactively hide kube object menu item", () => {
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
someObservable = observable.box(false);
|
||||
|
||||
@ -10,7 +10,7 @@ import type { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||
import { getApplicationBuilder } from "../../../../renderer/components/test-utils/get-application-builder";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../../../common/front-end-routing/front-end-route-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import React from "react";
|
||||
import { navigateToRouteInjectionToken } from "../../../../common/front-end-routing/navigate-to-route-injection-token";
|
||||
import { routeSpecificComponentInjectionToken } from "../../../../renderer/routes/route-specific-component-injection-token";
|
||||
@ -33,7 +33,10 @@ describe("disable kube object statuses when cluster is not relevant", () => {
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
isEnabledForClusterMock = asyncFn();
|
||||
|
||||
@ -29,7 +29,10 @@ describe("reactively hide kube object status", () => {
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.unoverride(extensionShouldBeEnabledForClusterFrameInjectable);
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable, testRouteComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
someObservable = observable.box(false);
|
||||
|
||||
@ -9,7 +9,7 @@ import { useFakeTime } from "../../../common/test-utils/use-fake-time";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { IAtom } from "mobx";
|
||||
import { createAtom, computed } from "mobx";
|
||||
import { runInAction, createAtom, computed } from "mobx";
|
||||
import { frontEndRouteInjectionToken } from "../../../common/front-end-routing/front-end-route-injection-token";
|
||||
import { routeSpecificComponentInjectionToken } from "../../../renderer/routes/route-specific-component-injection-token";
|
||||
import type { ApplicationBuilder } from "../../../renderer/components/test-utils/get-application-builder";
|
||||
@ -94,14 +94,17 @@ describe("show status for a kube object", () => {
|
||||
});
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(
|
||||
testRouteInjectable,
|
||||
testRouteComponentInjectable,
|
||||
infoStatusInjectable,
|
||||
warningStatusInjectable,
|
||||
criticalStatusInjectable,
|
||||
someAtomInjectable,
|
||||
);
|
||||
runInAction(() => {
|
||||
windowDi.register(
|
||||
testRouteInjectable,
|
||||
testRouteComponentInjectable,
|
||||
infoStatusInjectable,
|
||||
warningStatusInjectable,
|
||||
criticalStatusInjectable,
|
||||
someAtomInjectable,
|
||||
);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
@ -141,7 +144,9 @@ describe("show status for a kube object", () => {
|
||||
|
||||
describe("when status for irrelevant kube object kind emerges", () => {
|
||||
beforeEach(() => {
|
||||
windowDi.register(statusForIrrelevantKubeObjectKindInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(statusForIrrelevantKubeObjectKindInjectable);
|
||||
});
|
||||
|
||||
rerenderParent();
|
||||
});
|
||||
@ -161,7 +166,9 @@ describe("show status for a kube object", () => {
|
||||
|
||||
describe("when status for irrelevant kube object api version emerges", () => {
|
||||
beforeEach(() => {
|
||||
windowDi.register(statusForIrrelevantKubeObjectApiVersionInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(statusForIrrelevantKubeObjectApiVersionInjectable);
|
||||
});
|
||||
|
||||
rerenderParent();
|
||||
});
|
||||
|
||||
@ -7,7 +7,7 @@ import type { RenderResult } from "@testing-library/react";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import type { SidebarItemRegistration } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { sidebarItemsInjectionToken } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { noop } from "lodash/fp";
|
||||
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
@ -22,7 +22,9 @@ describe("cluster - order of sidebar items", () => {
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import directoryForLensLocalStorageInjectable from "../../common/directory-for-l
|
||||
import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token";
|
||||
import type { SidebarItemRegistration } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { sidebarItemsInjectionToken } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { noop } from "lodash/fp";
|
||||
import routeIsActiveInjectable from "../../renderer/routes/route-is-active.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token";
|
||||
@ -51,9 +51,11 @@ describe("cluster - sidebar and tab navigation for core", () => {
|
||||
describe("given core registrations", () => {
|
||||
beforeEach(() => {
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testRouteInjectable);
|
||||
windowDi.register(testRouteComponentInjectable);
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable);
|
||||
windowDi.register(testRouteComponentInjectable);
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import type { SidebarItemRegistration } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { sidebarItemsInjectionToken } from "../../renderer/components/layout/sidebar-items.injectable";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token";
|
||||
import React from "react";
|
||||
import isAllowedResourceInjectable from "../../common/utils/is-allowed-resource.injectable";
|
||||
@ -25,9 +25,11 @@ describe("cluster - visibility of sidebar items", () => {
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testRouteInjectable);
|
||||
windowDi.register(testRouteComponentInjectable);
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteInjectable);
|
||||
windowDi.register(testRouteComponentInjectable);
|
||||
windowDi.register(testSidebarItemsInjectable);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import React from "react";
|
||||
import getRandomIdInjectable from "../../../../../common/utils/get-random-id.injectable";
|
||||
import { workloadOverviewDetailInjectionToken } from "../../../../../renderer/components/+workloads-overview/workload-overview-details/workload-overview-detail-injection-token";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
|
||||
describe("order of workload overview details", () => {
|
||||
let rendered: RenderResult;
|
||||
@ -20,11 +20,13 @@ describe("order of workload overview details", () => {
|
||||
windowDi.unoverride(getRandomIdInjectable);
|
||||
windowDi.permitSideEffects(getRandomIdInjectable);
|
||||
|
||||
windowDi.register(
|
||||
someCoreItemWithLowOrderNumberInjectable,
|
||||
someCoreItemWithHighOrderNumberInjectable,
|
||||
someCoreItemWithDefaultOrderNumberInjectable,
|
||||
);
|
||||
runInAction(() => {
|
||||
windowDi.register(
|
||||
someCoreItemWithLowOrderNumberInjectable,
|
||||
someCoreItemWithHighOrderNumberInjectable,
|
||||
someCoreItemWithDefaultOrderNumberInjectable,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
builder.setEnvironmentToClusterFrame();
|
||||
|
||||
@ -167,6 +167,7 @@ describe("add custom helm repository in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "some-custom-repository", "http://some.url"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -365,6 +366,7 @@ describe("add custom helm repository in preferences", () => {
|
||||
"--cert-file",
|
||||
"some-cert-file",
|
||||
],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -119,6 +119,7 @@ describe("add helm repository from list in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "Some to be added repository", "some-other-url"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -227,6 +228,7 @@ describe("add helm repository from list in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "Some already active repository"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -11496,7 +11496,21 @@ exports[`installing helm chart from new tab given tab for installing chart was n
|
||||
<div
|
||||
class="drawer-title-text flex gaps align-center"
|
||||
>
|
||||
|
||||
some-release
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="icon"
|
||||
data-icon-name="content_copy"
|
||||
>
|
||||
content_copy
|
||||
</span>
|
||||
</i>
|
||||
<div>
|
||||
Copy
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="Icon material interactive focusable"
|
||||
|
||||
@ -29,6 +29,8 @@ import hostedClusterIdInjectable from "../../../renderer/cluster-frame-context/h
|
||||
import dockStoreInjectable from "../../../renderer/components/dock/dock/store.injectable";
|
||||
import readJsonFileInjectable from "../../../common/fs/read-json-file.injectable";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import callForHelmReleasesInjectable from "../../../renderer/components/+helm-releases/call-for-helm-releases/call-for-helm-releases.injectable";
|
||||
import callForHelmReleaseDetailsInjectable 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";
|
||||
|
||||
describe("installing helm chart from new tab", () => {
|
||||
let builder: ApplicationBuilder;
|
||||
@ -50,6 +52,9 @@ describe("installing helm chart from new tab", () => {
|
||||
callForCreateHelmReleaseMock = asyncFn();
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.override(callForHelmReleasesInjectable, () => async () => []);
|
||||
windowDi.override(callForHelmReleaseDetailsInjectable, () => () => new Promise(() => {}));
|
||||
|
||||
windowDi.override(
|
||||
directoryForLensLocalStorageInjectable,
|
||||
() => "/some-directory-for-lens-local-storage",
|
||||
|
||||
@ -69,6 +69,7 @@ describe("listing active helm repositories in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["env"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -76,6 +77,7 @@ describe("listing active helm repositories in preferences", () => {
|
||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "update"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -207,6 +209,7 @@ describe("listing active helm repositories in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "update"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -265,6 +268,7 @@ describe("listing active helm repositories in preferences", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
@ -400,6 +404,7 @@ describe("listing active helm repositories in preferences", () => {
|
||||
expect(execFileMock).not.toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "add", "bitnami", "https://charts.bitnami.com/bitnami"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -86,6 +86,7 @@ describe("remove helm repository from list of active repositories in preferences
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"some-helm-binary-path",
|
||||
["repo", "remove", "some-active-repository"],
|
||||
{ "maxBuffer": 34359738368 },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -288,15 +288,42 @@ describe("showing details for helm release", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("when release resolve with no data, renders", async () => {
|
||||
await callForHelmReleaseMock.resolve(undefined);
|
||||
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
describe("when call for release resolves with error", () => {
|
||||
beforeEach(async () => {
|
||||
await callForHelmReleaseMock.resolve({
|
||||
callWasSuccessful: false,
|
||||
error: "some-error",
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", async () => {
|
||||
expect(rendered.baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not show spinner anymore", () => {
|
||||
expect(
|
||||
rendered.queryByTestId("helm-release-detail-content-spinner"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows error message about missing release", () => {
|
||||
expect(
|
||||
rendered.getByTestId("helm-release-detail-error"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not call for release configuration", () => {
|
||||
expect(callForHelmReleaseConfigurationMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when details resolve", () => {
|
||||
describe("when call for release resolve with release", () => {
|
||||
beforeEach(async () => {
|
||||
await callForHelmReleaseMock.resolve(detailedReleaseFake);
|
||||
await callForHelmReleaseMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: detailedReleaseFake,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders", () => {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import React from "react";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import type { RenderResult } from "@testing-library/react";
|
||||
import { routeSpecificComponentInjectionToken } from "../renderer/routes/route-specific-component-injection-token";
|
||||
import { observer } from "mobx-react";
|
||||
@ -32,8 +32,10 @@ describe("navigating between routes", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testRouteWithoutPathParametersInjectable);
|
||||
windowDi.register(testRouteWithoutPathParametersComponentInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testRouteWithoutPathParametersInjectable);
|
||||
windowDi.register(testRouteWithoutPathParametersComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
rendered = await builder.render();
|
||||
@ -106,8 +108,10 @@ describe("navigating between routes", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(routeWithOptionalPathParametersInjectable);
|
||||
windowDi.register(routeWithOptionalPathParametersComponentInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(routeWithOptionalPathParametersInjectable);
|
||||
windowDi.register(routeWithOptionalPathParametersComponentInjectable);
|
||||
});
|
||||
});
|
||||
|
||||
rendered = await builder.render();
|
||||
|
||||
@ -25,6 +25,7 @@ import reloadLogsInjectable from "../../renderer/components/dock/logs/reload-log
|
||||
import setLogTabDataInjectable from "../../renderer/components/dock/logs/set-log-tab-data.injectable";
|
||||
import stopLoadingLogsInjectable from "../../renderer/components/dock/logs/stop-loading-logs.injectable";
|
||||
import { dockerPod } from "../../renderer/components/dock/logs/__test__/pod.mock";
|
||||
import type { Container } from "../../common/k8s-api/endpoints";
|
||||
|
||||
describe("download logs options in pod logs dock tab", () => {
|
||||
let rendered: RenderResult;
|
||||
@ -85,10 +86,10 @@ describe("download logs options in pod logs dock tab", () => {
|
||||
const windowDi = builder.applicationWindow.only.di;
|
||||
const pod = dockerPod;
|
||||
const createLogsTab = windowDi.inject(createPodLogsTabInjectable);
|
||||
const container = {
|
||||
const container: Container = {
|
||||
name: "docker-exporter",
|
||||
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
|
||||
imagePullPolicy: "pull",
|
||||
imagePullPolicy: "Always",
|
||||
};
|
||||
|
||||
const dockStore = windowDi.inject(dockStoreInjectable);
|
||||
@ -9,7 +9,7 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import currentPathInjectable from "../../renderer/routes/current-path.injectable";
|
||||
import { frontEndRouteInjectionToken } from "../../common/front-end-routing/front-end-route-injection-token";
|
||||
import { computed } from "mobx";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { preferenceNavigationItemInjectionToken } from "../../renderer/components/+preferences/preferences-navigation/preference-navigation-items.injectable";
|
||||
import routeIsActiveInjectable from "../../renderer/routes/route-is-active.injectable";
|
||||
import { Preferences } from "../../renderer/components/+preferences";
|
||||
@ -30,11 +30,13 @@ describe("preferences - closing-preferences", () => {
|
||||
builder = getApplicationBuilder();
|
||||
|
||||
builder.beforeWindowStart((windowDi) => {
|
||||
windowDi.register(testPreferencesRouteInjectable);
|
||||
windowDi.register(testPreferencesRouteComponentInjectable);
|
||||
windowDi.register(testFrontPageRouteInjectable);
|
||||
windowDi.register(testFrontPageRouteComponentInjectable);
|
||||
windowDi.register(testNavigationItemInjectable);
|
||||
runInAction(() => {
|
||||
windowDi.register(testPreferencesRouteInjectable);
|
||||
windowDi.register(testPreferencesRouteComponentInjectable);
|
||||
windowDi.register(testFrontPageRouteInjectable);
|
||||
windowDi.register(testFrontPageRouteComponentInjectable);
|
||||
windowDi.register(testNavigationItemInjectable);
|
||||
});
|
||||
|
||||
windowDi.override(navigateToFrontPageInjectable, (di) => {
|
||||
const navigateToRoute = di.inject(navigateToRouteInjectionToken);
|
||||
|
||||
@ -13,6 +13,7 @@ import focusApplicationInjectable from "../../main/electron-app/features/focus-a
|
||||
import type { CreateElectronWindow } from "../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
|
||||
import createElectronWindowInjectable from "../../main/start-main-application/lens-window/application-window/create-electron-window.injectable";
|
||||
import splashWindowInjectable from "../../main/start-main-application/lens-window/splash-window/splash-window.injectable";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
describe("opening application window using tray", () => {
|
||||
describe("given application has started", () => {
|
||||
@ -61,10 +62,12 @@ describe("opening application window using tray", () => {
|
||||
return browserWindow;
|
||||
});
|
||||
|
||||
(mainDi as any).decorateFunction(
|
||||
createElectronWindowInjectable,
|
||||
createElectronWindowMock,
|
||||
);
|
||||
runInAction(() => {
|
||||
(mainDi as any).decorateFunction(
|
||||
createElectronWindowInjectable,
|
||||
createElectronWindowMock,
|
||||
);
|
||||
});
|
||||
|
||||
expectWindowsToBeOpen = expectWindowsToBeOpenFor(builder);
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ import type { ApplicationBuilder } from "../../renderer/components/test-utils/ge
|
||||
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||
import getRandomIdInjectable from "../../common/utils/get-random-id.injectable";
|
||||
import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake";
|
||||
import { computed } from "mobx";
|
||||
|
||||
describe("status-bar-items-originating-from-extensions", () => {
|
||||
let applicationBuilder: ApplicationBuilder;
|
||||
@ -65,7 +66,7 @@ describe("status-bar-items-originating-from-extensions", () => {
|
||||
|
||||
const rightSide = rendered.getByTestId("status-bar-right");
|
||||
|
||||
const actual = getTestStatusBarTexts(rightSide, [
|
||||
const actual = getExpectedTestStatusBarTexts(rightSide, [
|
||||
"extension1",
|
||||
"extension2",
|
||||
]);
|
||||
@ -95,6 +96,13 @@ describe("status-bar-items-originating-from-extensions", () => {
|
||||
position: "right" as const,
|
||||
},
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="some-testId">right4</div>,
|
||||
position: "right" as const,
|
||||
},
|
||||
visible: computed(() => false),
|
||||
},
|
||||
{
|
||||
components: {
|
||||
Item: () => <div data-testid="some-testId">left1</div>,
|
||||
@ -121,7 +129,7 @@ describe("status-bar-items-originating-from-extensions", () => {
|
||||
it("shows right side status bar items in the correct order", () => {
|
||||
const rightSide = rendered.getByTestId("status-bar-right");
|
||||
|
||||
const actual = getTestStatusBarTexts(rightSide, [
|
||||
const actual = getExpectedTestStatusBarTexts(rightSide, [
|
||||
"right1",
|
||||
"right2",
|
||||
"right3",
|
||||
@ -130,10 +138,16 @@ describe("status-bar-items-originating-from-extensions", () => {
|
||||
expect(actual).toEqual(["right3", "right2", "right1"]);
|
||||
});
|
||||
|
||||
it("doesn't show invisible status bar item", () => {
|
||||
const rightSide = rendered.getByTestId("status-bar-right");
|
||||
|
||||
expect(getTestStatusBarTexts(rightSide)).not.toContain("right4");
|
||||
});
|
||||
|
||||
it("shows left side status bar items in the correct order", () => {
|
||||
const leftSide = rendered.getByTestId("status-bar-left");
|
||||
|
||||
const actual = getTestStatusBarTexts(leftSide, ["left2", "left1"]);
|
||||
const actual = getExpectedTestStatusBarTexts(leftSide, ["left2", "left1"]);
|
||||
|
||||
expect(actual).toEqual(["left1", "left2"]);
|
||||
});
|
||||
@ -149,7 +163,9 @@ describe("status-bar-items-originating-from-extensions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
const getTestStatusBarTexts = (actual: HTMLElement, expectedTexts: string[]) =>
|
||||
const getTestStatusBarTexts = (actual: HTMLElement) =>
|
||||
Array.from(actual.children)
|
||||
.map((elem) => elem.textContent)
|
||||
.filter((elem) => elem && expectedTexts.includes(elem));
|
||||
.map((elem) => String(elem.textContent));
|
||||
|
||||
const getExpectedTestStatusBarTexts = (actual: HTMLElement, expectedTexts: string[]) =>
|
||||
getTestStatusBarTexts(actual).filter((textContent) => textContent && expectedTexts.includes(textContent));
|
||||
|
||||
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { computed, runInAction } from "mobx";
|
||||
import type { DiContainer } from "@ogre-tools/injectable";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting";
|
||||
import telemetryWhiteListForFunctionsInjectable from "./renderer/telemetry-white-list-for-functions.injectable";
|
||||
import emitEventInjectable from "../../common/app-event-bus/emit-event.injectable";
|
||||
|
||||
describe("emit-telemetry-from-specific-function-calls", () => {
|
||||
let di: DiContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
});
|
||||
|
||||
describe("given a telemetry white-list for injectables which instantiate a function", () => {
|
||||
let emitEventMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
di.override(telemetryWhiteListForFunctionsInjectable, () => [
|
||||
"some-white-listed-function",
|
||||
]);
|
||||
|
||||
emitEventMock = jest.fn();
|
||||
di.override(emitEventInjectable, () => emitEventMock);
|
||||
});
|
||||
|
||||
describe("given instances of white-listed, non-white-listed and tagged functions", () => {
|
||||
let whiteListedFunctionMock: jest.Mock;
|
||||
let nonWhiteListedFunctionMock: jest.Mock;
|
||||
let taggedFunctionMock: jest.Mock;
|
||||
let injectedWhiteListedFunction: jest.Mock;
|
||||
let injectedNonWhiteListedFunction: jest.Mock;
|
||||
let injectedTaggedFunction: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
whiteListedFunctionMock = jest.fn();
|
||||
nonWhiteListedFunctionMock = jest.fn();
|
||||
taggedFunctionMock = jest.fn();
|
||||
|
||||
const whiteListedInjectable = getInjectable({
|
||||
id: "some-white-listed-function",
|
||||
instantiate: () => whiteListedFunctionMock,
|
||||
});
|
||||
|
||||
const nonWhiteListedInjectable = getInjectable({
|
||||
id: "some-non-white-listed-function",
|
||||
instantiate: () => nonWhiteListedFunctionMock,
|
||||
});
|
||||
|
||||
const taggedInjectable = getInjectable({
|
||||
id: "some-tagged-function",
|
||||
instantiate: () => taggedFunctionMock,
|
||||
tags: ["emit-telemetry"],
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
di.register(whiteListedInjectable);
|
||||
di.register(nonWhiteListedInjectable);
|
||||
di.register(taggedInjectable);
|
||||
});
|
||||
|
||||
injectedWhiteListedFunction = di.inject(whiteListedInjectable);
|
||||
injectedNonWhiteListedFunction = di.inject(nonWhiteListedInjectable);
|
||||
injectedTaggedFunction = di.inject(taggedInjectable);
|
||||
});
|
||||
|
||||
it("telemetry is not emitted yet", () => {
|
||||
expect(emitEventMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("when the white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
injectedWhiteListedFunction("some-arg", "some-other-arg");
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-white-listed-function",
|
||||
params: { args: ["some-arg", "some-other-arg"] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the white-listed function is called with MobX reactive content", () => {
|
||||
beforeEach(() => {
|
||||
const someComputedProperty = computed(() => "some-computed-value");
|
||||
|
||||
const someObservable = {
|
||||
someStaticProperty: "some-static-value",
|
||||
someComputedProperty,
|
||||
};
|
||||
|
||||
injectedWhiteListedFunction(someObservable);
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus without MobX internals or computeds", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-white-listed-function",
|
||||
|
||||
params: {
|
||||
args: [
|
||||
{
|
||||
someStaticProperty: "some-static-value",
|
||||
someComputedProperty: "some-computed-value",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the non-white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
injectedNonWhiteListedFunction();
|
||||
});
|
||||
|
||||
it("telemetry is not emitted", () => {
|
||||
expect(emitEventMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the tagged, but not white-listed function is called", () => {
|
||||
beforeEach(() => {
|
||||
injectedTaggedFunction("some-arg", "some-other-arg");
|
||||
});
|
||||
|
||||
it("telemetry is emitted in event bus", () => {
|
||||
expect(emitEventMock).toHaveBeenCalledWith({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: "some-tagged-function",
|
||||
params: { args: ["some-arg", "some-other-arg"] },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
28
src/features/telemetry/renderer/emit-telemetry.injectable.ts
Normal file
28
src/features/telemetry/renderer/emit-telemetry.injectable.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 emitEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
||||
import { toJS, observable } from "mobx";
|
||||
|
||||
const emitTelemetryInjectable = getInjectable({
|
||||
id: "emit-telemetry",
|
||||
|
||||
instantiate: (di) => {
|
||||
const emitEvent = di.inject(emitEventInjectable);
|
||||
|
||||
return ({ action, args }: { action: string; args: any[] }) => {
|
||||
emitEvent({
|
||||
destination: "auto-capture",
|
||||
action: "telemetry-from-business-action",
|
||||
name: action,
|
||||
params: { args: toJS(observable(args)) },
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
export default emitTelemetryInjectable;
|
||||
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type {
|
||||
DiContainerForInjection,
|
||||
Injectable,
|
||||
} from "@ogre-tools/injectable";
|
||||
|
||||
import {
|
||||
lifecycleEnum,
|
||||
getInjectable,
|
||||
instantiationDecoratorToken,
|
||||
} from "@ogre-tools/injectable";
|
||||
import assert from "assert";
|
||||
|
||||
import { isFunction } from "lodash/fp";
|
||||
import emitTelemetryInjectable from "./emit-telemetry.injectable";
|
||||
import telemetryWhiteListForFunctionsInjectable from "./telemetry-white-list-for-functions.injectable";
|
||||
|
||||
const telemetryDecoratorInjectable = getInjectable({
|
||||
id: "telemetry-decorator",
|
||||
|
||||
instantiate: (diForDecorator) => {
|
||||
const emitTelemetry = diForDecorator.inject(emitTelemetryInjectable);
|
||||
|
||||
const whiteList = diForDecorator.inject(
|
||||
telemetryWhiteListForFunctionsInjectable,
|
||||
);
|
||||
|
||||
const shouldEmitTelemetry = shouldEmitTelemetryFor(whiteList);
|
||||
|
||||
return {
|
||||
decorate:
|
||||
(instantiateToBeDecorated: any) =>
|
||||
(di: DiContainerForInjection, instantiationParameter: any) => {
|
||||
const instance = instantiateToBeDecorated(di, instantiationParameter);
|
||||
|
||||
if (isFunction(instance)) {
|
||||
return (...args: any[]) => {
|
||||
const currentContext = di.context.at(-1);
|
||||
|
||||
assert(currentContext);
|
||||
|
||||
if (shouldEmitTelemetry(currentContext.injectable)) {
|
||||
emitTelemetry({ action: currentContext.injectable.id, args });
|
||||
}
|
||||
|
||||
return instance(...args);
|
||||
};
|
||||
}
|
||||
|
||||
return instance;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
decorable: false,
|
||||
// Todo: this is required because of imperfect typing in injectable.
|
||||
lifecycle: lifecycleEnum.singleton,
|
||||
injectionToken: instantiationDecoratorToken,
|
||||
});
|
||||
|
||||
const shouldEmitTelemetryFor =
|
||||
(whiteList: string[]) => (injectable: Injectable<any, any, any>) =>
|
||||
injectable.tags?.includes("emit-telemetry") ||
|
||||
whiteList.includes(injectable.id);
|
||||
|
||||
export default telemetryDecoratorInjectable;
|
||||
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 navigateTo = [
|
||||
"navigate-to-preference-tab-id",
|
||||
"navigate-to-preference-tab",
|
||||
"navigate-to-front-page",
|
||||
"navigate-to-horizontal-pod-autoscalers",
|
||||
"navigate-to-secrets",
|
||||
"navigate-to-limit-ranges",
|
||||
"navigate-to-pod-disruption-budgets",
|
||||
"navigate-to-resource-quotas",
|
||||
"navigate-to-priority-classes",
|
||||
"navigate-to-config-maps",
|
||||
"navigate-to-nodes",
|
||||
"navigate-to-port-forwards",
|
||||
"navigate-to-endpoints",
|
||||
"navigate-to-network-policies",
|
||||
"navigate-to-ingresses",
|
||||
"navigate-to-services",
|
||||
"navigate-to-persistent-volumes",
|
||||
"navigate-to-persistent-volume-claims",
|
||||
"navigate-to-storage-classes",
|
||||
"navigate-to-namespaces",
|
||||
"navigate-to-statefulsets",
|
||||
"navigate-to-cron-jobs",
|
||||
"navigate-to-pods",
|
||||
"navigate-to-replicasets",
|
||||
"navigate-to-daemonsets",
|
||||
"navigate-to-jobs",
|
||||
"navigate-to-workloads-overview",
|
||||
"navigate-to-deployments",
|
||||
"navigate-to-crd-list",
|
||||
"navigate-to-custom-resources",
|
||||
"navigate-to-pod-security-policies",
|
||||
"navigate-to-cluster-role-bindings",
|
||||
"navigate-to-roles",
|
||||
"navigate-to-role-bindings",
|
||||
"navigate-to-service-accounts",
|
||||
"navigate-to-cluster-roles",
|
||||
"navigate-to-events",
|
||||
"navigate-to-cluster-overview",
|
||||
"navigate-to-helm-releases",
|
||||
"navigate-to-helm-charts",
|
||||
"navigate-to-extension-preferences",
|
||||
"navigate-to-app-preferences",
|
||||
"navigate-to-proxy-preferences",
|
||||
"navigate-to-preferences",
|
||||
"navigate-to-terminal-preferences",
|
||||
"navigate-to-telemetry-preferences",
|
||||
"navigate-to-kubernetes-preferences",
|
||||
"navigate-to-editor-preferences",
|
||||
"navigate-to-add-cluster",
|
||||
"navigate-to-catalog",
|
||||
"navigate-to-welcome",
|
||||
"navigate-to-extensions",
|
||||
"navigate-to-cluster-view",
|
||||
"navigate-to-entity-settings",
|
||||
];
|
||||
|
||||
const helmInjectableIds = [
|
||||
"update-helm-release",
|
||||
"install-helm-chart",
|
||||
"delete-helm-release",
|
||||
"list-helm-charts",
|
||||
"add-helm-repository-channel",
|
||||
"remove-helm-repository-channel",
|
||||
"select-helm-repository",
|
||||
"call-for-helm-chart-versions",
|
||||
];
|
||||
|
||||
const kubeConfigActions = [
|
||||
"create-cluster",
|
||||
"add-sync-entries",
|
||||
"open-delete-cluster-dialog",
|
||||
];
|
||||
|
||||
const extensions = [
|
||||
"enable-extension",
|
||||
"disable-extension",
|
||||
"attempt-install",
|
||||
"unpack-extension",
|
||||
"install-extension-from-input",
|
||||
"confirm-uninstall-extension",
|
||||
"uninstall-extension",
|
||||
];
|
||||
|
||||
const externalActions = [
|
||||
"open-link-in-browser",
|
||||
];
|
||||
|
||||
const uiInteraction = [
|
||||
"show-details",
|
||||
];
|
||||
|
||||
const terminal = [
|
||||
"create-terminal-tab",
|
||||
];
|
||||
|
||||
const logs = [
|
||||
"get-logs",
|
||||
];
|
||||
|
||||
const resourceEditor = [
|
||||
"create-edit-resource-tab",
|
||||
"clear-edit-resource-tab",
|
||||
];
|
||||
|
||||
const telemetryWhiteListForFunctionsInjectable = getInjectable({
|
||||
id: "telemetry-white-list-for-functions",
|
||||
instantiate: () => [
|
||||
...navigateTo,
|
||||
...helmInjectableIds,
|
||||
...kubeConfigActions,
|
||||
...extensions,
|
||||
...externalActions,
|
||||
...uiInteraction,
|
||||
...terminal,
|
||||
...logs,
|
||||
...resourceEditor,
|
||||
],
|
||||
decorable: false,
|
||||
});
|
||||
|
||||
export default telemetryWhiteListForFunctionsInjectable;
|
||||
@ -207,6 +207,8 @@ export class ContextHandler implements ClusterContextHandler {
|
||||
}
|
||||
|
||||
stopServer() {
|
||||
this.prometheus = undefined;
|
||||
this.prometheusProvider = undefined;
|
||||
this.kubeAuthProxy?.exit();
|
||||
this.kubeAuthProxy = undefined;
|
||||
this.apiTarget = undefined;
|
||||
|
||||
@ -5,24 +5,27 @@
|
||||
import { createContainer } from "@ogre-tools/injectable";
|
||||
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import { runInAction } from "mobx";
|
||||
import { Environments, setLegacyGlobalDiForExtensionApi } from "../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
|
||||
|
||||
export const getDi = () => {
|
||||
const di = createContainer("main");
|
||||
|
||||
registerMobX(di);
|
||||
|
||||
autoRegister({
|
||||
di,
|
||||
requireContexts: [
|
||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../extensions", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../common", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../features", true, /.*\/(main|common)\/.*\.injectable\.(ts|tsx)$/),
|
||||
],
|
||||
});
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||
|
||||
runInAction(() => {
|
||||
registerMobX(di);
|
||||
|
||||
autoRegister({
|
||||
di,
|
||||
requireContexts: [
|
||||
require.context("./", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../extensions", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../common", true, /\.injectable\.(ts|tsx)$/),
|
||||
require.context("../features", true, /.*\/(main|common)\/.*\.injectable\.(ts|tsx)$/),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
return di;
|
||||
};
|
||||
|
||||
@ -55,7 +55,7 @@ import setupRunnablesBeforeClosingOfApplicationInjectable from "./electron-app/r
|
||||
import showMessagePopupInjectable from "./electron-app/features/show-message-popup.injectable";
|
||||
import clusterFramesInjectable from "../common/cluster-frames.injectable";
|
||||
import type { ClusterFrameInfo } from "../common/cluster-frames";
|
||||
import { observable } from "mobx";
|
||||
import { observable, runInAction } from "mobx";
|
||||
import waitForElectronToBeReadyInjectable from "./electron-app/features/wait-for-electron-to-be-ready.injectable";
|
||||
import setupListenerForCurrentClusterFrameInjectable from "./start-main-application/lens-window/current-cluster-frame/setup-listener-for-current-cluster-frame.injectable";
|
||||
import setupRunnablesAfterWindowIsOpenedInjectable from "./electron-app/runnables/setup-runnables-after-window-is-opened.injectable";
|
||||
@ -83,12 +83,10 @@ import getHelmChartValuesInjectable from "./helm/helm-service/get-helm-chart-val
|
||||
import listHelmChartsInjectable from "./helm/helm-service/list-helm-charts.injectable";
|
||||
import deleteHelmReleaseInjectable from "./helm/helm-service/delete-helm-release.injectable";
|
||||
import getHelmReleaseHistoryInjectable from "./helm/helm-service/get-helm-release-history.injectable";
|
||||
import getHelmReleaseInjectable from "./helm/helm-service/get-helm-release.injectable";
|
||||
import getHelmReleaseValuesInjectable from "./helm/helm-service/get-helm-release-values.injectable";
|
||||
import installHelmChartInjectable from "./helm/helm-service/install-helm-chart.injectable";
|
||||
import listHelmReleasesInjectable from "./helm/helm-service/list-helm-releases.injectable";
|
||||
import rollbackHelmReleaseInjectable from "./helm/helm-service/rollback-helm-release.injectable";
|
||||
import updateHelmReleaseInjectable from "./helm/helm-service/update-helm-release.injectable";
|
||||
import waitUntilBundledExtensionsAreLoadedInjectable from "./start-main-application/lens-window/application-window/wait-until-bundled-extensions-are-loaded.injectable";
|
||||
import { registerMobX } from "@ogre-tools/injectable-extension-for-mobx";
|
||||
import electronInjectable from "./utils/resolve-system-proxy/electron.injectable";
|
||||
@ -103,19 +101,21 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
|
||||
const di = createContainer("main");
|
||||
|
||||
registerMobX(di);
|
||||
|
||||
setLegacyGlobalDiForExtensionApi(di, Environments.main);
|
||||
|
||||
di.preventSideEffects();
|
||||
|
||||
const injectables: Injectable<any, any, any>[] = (global as any).mainInjectablePaths.map(
|
||||
(filePath: string) => require(filePath).default,
|
||||
);
|
||||
|
||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||
di.register(...chunkInjectables);
|
||||
});
|
||||
runInAction(() => {
|
||||
registerMobX(di);
|
||||
|
||||
di.preventSideEffects();
|
||||
chunk(100)(injectables).forEach(chunkInjectables => {
|
||||
di.register(...chunkInjectables);
|
||||
});
|
||||
});
|
||||
|
||||
if (doGeneralOverrides) {
|
||||
const globalOverrides: GlobalOverride[] = (global as any).mainGlobalOverridePaths.map(
|
||||
@ -166,12 +166,10 @@ export function getDiForUnitTesting(opts: { doGeneralOverrides?: boolean } = {})
|
||||
listHelmChartsInjectable,
|
||||
deleteHelmReleaseInjectable,
|
||||
getHelmReleaseHistoryInjectable,
|
||||
getHelmReleaseInjectable,
|
||||
getHelmReleaseValuesInjectable,
|
||||
installHelmChartInjectable,
|
||||
listHelmReleasesInjectable,
|
||||
rollbackHelmReleaseInjectable,
|
||||
updateHelmReleaseInjectable,
|
||||
writeJsonFileInjectable,
|
||||
readJsonFileInjectable,
|
||||
readFileInjectable,
|
||||
|
||||
@ -8,16 +8,20 @@ import helmBinaryPathInjectable from "../helm-binary-path.injectable";
|
||||
import type { AsyncResult } from "../../../common/utils/async-result";
|
||||
import { getErrorMessage } from "../../../common/utils/get-error-message";
|
||||
|
||||
export type ExecHelm = (args: string[]) => Promise<AsyncResult<string>>;
|
||||
|
||||
const execHelmInjectable = getInjectable({
|
||||
id: "exec-helm",
|
||||
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): ExecHelm => {
|
||||
const execFile = di.inject(execFileInjectable);
|
||||
const helmBinaryPath = di.inject(helmBinaryPathInjectable);
|
||||
|
||||
return async (...args: string[]): Promise<AsyncResult<string>> => {
|
||||
return async (args) => {
|
||||
try {
|
||||
const response = await execFile(helmBinaryPath, args);
|
||||
const response = await execFile(helmBinaryPath, args, {
|
||||
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
||||
});
|
||||
|
||||
return { callWasSuccessful: true, response };
|
||||
} catch (error) {
|
||||
|
||||
@ -18,7 +18,7 @@ const getHelmEnvInjectable = getInjectable({
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (): Promise<AsyncResult<HelmEnv>> => {
|
||||
const result = await execHelm("env");
|
||||
const result = await execHelm(["env"]);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return { callWasSuccessful: false, error: result.error };
|
||||
|
||||
@ -7,10 +7,8 @@ import tempy from "tempy";
|
||||
import fse from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
import { toCamelCase } from "../../common/utils/camelCase";
|
||||
import { execFile } from "child_process";
|
||||
import { execHelm } from "./exec";
|
||||
import assert from "assert";
|
||||
import type { JsonObject, JsonValue } from "type-fest";
|
||||
import type { JsonValue } from "type-fest";
|
||||
import { isObject, json } from "../../common/utils";
|
||||
|
||||
export async function listReleases(pathToKubeconfig: string, namespace?: string): Promise<Record<string, any>[]> {
|
||||
@ -77,55 +75,6 @@ export async function installChart(chart: string, values: JsonValue, name: strin
|
||||
}
|
||||
}
|
||||
|
||||
export async function upgradeRelease(name: string, chart: string, values: any, namespace: string, version: string, kubeconfigPath: string, kubectlPath: string) {
|
||||
const valuesFilePath = tempy.file({ name: "values.yaml" });
|
||||
|
||||
await fse.writeFile(valuesFilePath, yaml.dump(values));
|
||||
|
||||
const args = [
|
||||
"upgrade",
|
||||
name,
|
||||
chart,
|
||||
"--version", version,
|
||||
"--values", valuesFilePath,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
];
|
||||
|
||||
try {
|
||||
const output = await execHelm(args);
|
||||
|
||||
return {
|
||||
log: output,
|
||||
release: await getRelease(name, namespace, kubeconfigPath, kubectlPath),
|
||||
};
|
||||
} finally {
|
||||
await fse.unlink(valuesFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRelease(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||
const args = [
|
||||
"status",
|
||||
name,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
"--output", "json",
|
||||
];
|
||||
|
||||
const release = json.parse(await execHelm(args, {
|
||||
maxBuffer: 32 * 1024 * 1024 * 1024, // 32 MiB
|
||||
}));
|
||||
|
||||
if (!isObject(release) || Array.isArray(release)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
release.resources = await getResources(name, namespace, kubeconfigPath, kubectlPath);
|
||||
|
||||
return release;
|
||||
}
|
||||
|
||||
export async function deleteRelease(name: string, namespace: string, kubeconfigPath: string) {
|
||||
return execHelm([
|
||||
"delete",
|
||||
@ -180,62 +129,3 @@ export async function rollback(name: string, namespace: string, revision: number
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
]);
|
||||
}
|
||||
|
||||
async function getResources(name: string, namespace: string, kubeconfigPath: string, kubectlPath: string) {
|
||||
const helmArgs = [
|
||||
"get",
|
||||
"manifest",
|
||||
name,
|
||||
"--namespace", namespace,
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
];
|
||||
const kubectlArgs = [
|
||||
"get",
|
||||
"--kubeconfig", kubeconfigPath,
|
||||
"-f", "-",
|
||||
"--output", "json",
|
||||
// Temporary workaround for https://github.com/lensapp/lens/issues/6031
|
||||
// and other potential issues where resources can't be found. Showing
|
||||
// no resources is better than the app hard-locking, and at least
|
||||
// the helm metadata is shown.
|
||||
"--ignore-not-found",
|
||||
];
|
||||
|
||||
try {
|
||||
const helmOutput = await execHelm(helmArgs);
|
||||
|
||||
return new Promise<JsonObject[]>((resolve, reject) => {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
const kubectl = execFile(kubectlPath, kubectlArgs);
|
||||
|
||||
kubectl
|
||||
.on("exit", (code, signal) => {
|
||||
if (typeof code === "number") {
|
||||
if (code === 0) {
|
||||
if (stdout === "") {
|
||||
resolve([]);
|
||||
} else {
|
||||
const output = json.parse(stdout) as { items: JsonObject[] };
|
||||
|
||||
resolve(output.items);
|
||||
}
|
||||
} else {
|
||||
reject(stderr);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`Kubectl exited with signal ${signal}`));
|
||||
}
|
||||
})
|
||||
.on("error", reject);
|
||||
|
||||
assert(kubectl.stderr && kubectl.stdout && kubectl.stdin, "For some reason the IO streams are undefined");
|
||||
|
||||
kubectl.stderr.on("data", output => stderr += output);
|
||||
kubectl.stdout.on("data", output => stdout += output);
|
||||
kubectl.stdin.end(helmOutput);
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 { AsyncResult } from "../../../../../common/utils/async-result";
|
||||
import execHelmInjectable from "../../../exec-helm/exec-helm.injectable";
|
||||
import yaml from "js-yaml";
|
||||
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
|
||||
|
||||
const callForHelmManifestInjectable = getInjectable({
|
||||
id: "call-for-helm-manifest",
|
||||
|
||||
instantiate: (di) => {
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
|
||||
return async (
|
||||
name: string,
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
): Promise<AsyncResult<KubeJsonApiData[]>> => {
|
||||
const result = await execHelm([
|
||||
"get",
|
||||
"manifest",
|
||||
name,
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
]);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return { callWasSuccessful: false, error: result.error };
|
||||
}
|
||||
|
||||
return {
|
||||
callWasSuccessful: true,
|
||||
response: yaml
|
||||
.loadAll(result.response)
|
||||
.filter((manifest) => !!manifest) as KubeJsonApiData[],
|
||||
};
|
||||
};
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
export default callForHelmManifestInjectable;
|
||||
@ -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 type { JsonObject } from "type-fest";
|
||||
import { json } from "../../../../../common/utils";
|
||||
import yaml from "js-yaml";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input/exec-file-with-input.injectable";
|
||||
import { getErrorMessage } from "../../../../../common/utils/get-error-message";
|
||||
import { map } from "lodash/fp";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import type { KubeJsonApiData } from "../../../../../common/k8s-api/kube-json-api";
|
||||
|
||||
export type CallForKubeResourcesByManifest = (
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
kubectlPath: string,
|
||||
resourceManifests: KubeJsonApiData[]
|
||||
) => Promise<JsonObject[]>;
|
||||
|
||||
const callForKubeResourcesByManifestInjectable = getInjectable({
|
||||
id: "call-for-kube-resources-by-manifest",
|
||||
|
||||
instantiate: (di): CallForKubeResourcesByManifest => {
|
||||
const execFileWithInput = di.inject(execFileWithInputInjectable);
|
||||
|
||||
return async (
|
||||
namespace,
|
||||
kubeconfigPath,
|
||||
kubectlPath,
|
||||
resourceManifests,
|
||||
) => {
|
||||
const input = pipeline(
|
||||
resourceManifests,
|
||||
map((manifest) => yaml.dump(manifest)),
|
||||
wideJoin("---\n"),
|
||||
);
|
||||
|
||||
const result = await execFileWithInput({
|
||||
filePath: kubectlPath,
|
||||
input,
|
||||
|
||||
commandArguments: [
|
||||
"get",
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
"-f",
|
||||
"-",
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--output",
|
||||
"json",
|
||||
],
|
||||
});
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
const errorMessage = getErrorMessage(result.error);
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const output = json.parse(result.response) as { items: JsonObject[] };
|
||||
|
||||
return output.items;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default callForKubeResourcesByManifestInjectable;
|
||||
|
||||
const wideJoin = (joiner: string) => (items: string[]) =>
|
||||
`${joiner}${items.join(joiner)}${joiner}`;
|
||||
@ -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 "../../../../../../common/test-utils/get-global-override";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input.injectable";
|
||||
|
||||
export default getGlobalOverride(execFileWithInputInjectable, () => () => {
|
||||
throw new Error(
|
||||
"Tried to call exec file with input without explicit override",
|
||||
);
|
||||
});
|
||||
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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 { AsyncResult } from "../../../../../../common/utils/async-result";
|
||||
import nonPromiseExecFileInjectable from "./non-promise-exec-file.injectable";
|
||||
import { isNumber } from "../../../../../../common/utils";
|
||||
import assert from "assert";
|
||||
import type { ChildProcess } from "child_process";
|
||||
|
||||
export type ExecFileWithInput = (options: {
|
||||
filePath: string;
|
||||
commandArguments: string[];
|
||||
input: string;
|
||||
}) => Promise<AsyncResult<string, unknown>>;
|
||||
|
||||
const execFileWithInputInjectable = getInjectable({
|
||||
id: "exec-file-with-input",
|
||||
|
||||
instantiate: (di): ExecFileWithInput => {
|
||||
const execFile = di.inject(nonPromiseExecFileInjectable);
|
||||
|
||||
return async ({ filePath, commandArguments, input }) =>
|
||||
new Promise((resolve) => {
|
||||
let execution: ChildProcess;
|
||||
|
||||
try {
|
||||
execution = execFile(filePath, commandArguments, {
|
||||
maxBuffer: 8 * 1024 * 1024 * 1024, // 8 MiB
|
||||
});
|
||||
} catch (e) {
|
||||
resolve({ callWasSuccessful: false, error: e });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert(execution.stdout, "stdout is not defined");
|
||||
assert(execution.stderr, "stderr is not defined");
|
||||
assert(execution.stdin, "stdin is not defined");
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
execution.stdout.on("data", (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
execution.stderr.on("data", (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
execution.on("error", (error) =>
|
||||
resolve({ callWasSuccessful: false, error }),
|
||||
);
|
||||
|
||||
execution.on("exit", (code, signal) => {
|
||||
if (!isNumber(code)) {
|
||||
/**
|
||||
* According to https://nodejs.org/api/child_process.html#class-childprocess (section about the "exit" event)
|
||||
* it says the following:
|
||||
*
|
||||
* If the process exited, code is the final exit code of the process, otherwise null.
|
||||
* If the process terminated due to receipt of a signal, signal is the string name of the signal, otherwise null.
|
||||
* One of the two will always be non-null.
|
||||
*/
|
||||
resolve({
|
||||
callWasSuccessful: false,
|
||||
error: `Exited via ${signal}`,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (code !== 0) {
|
||||
resolve({
|
||||
callWasSuccessful: false,
|
||||
error: stderr ? stderr : `Failed with error: ${signal}`,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
resolve({ callWasSuccessful: true, response: stdout });
|
||||
});
|
||||
|
||||
execution.stdin.end(input);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default execFileWithInputInjectable;
|
||||
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getDiForUnitTesting } from "../../../../../getDiForUnitTesting";
|
||||
import type { ExecFileWithInput } from "./exec-file-with-input.injectable";
|
||||
import execFileWithInputInjectable from "./exec-file-with-input.injectable";
|
||||
import type { AsyncResult } from "../../../../../../common/utils/async-result";
|
||||
import nonPromiseExecFileInjectable from "./non-promise-exec-file.injectable";
|
||||
import { getPromiseStatus } from "../../../../../../common/test-utils/get-promise-status";
|
||||
import EventEmitter from "events";
|
||||
|
||||
describe("exec-file-with-input", () => {
|
||||
let execFileWithInput: ExecFileWithInput;
|
||||
let execFileMock: jest.Mock;
|
||||
|
||||
let executionStub: EventEmitter & {
|
||||
stdin: { end: (chunk: any) => void };
|
||||
stdout: EventEmitter;
|
||||
stderr: EventEmitter;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
di.unoverride(execFileWithInputInjectable);
|
||||
|
||||
executionStub = Object.assign(new EventEmitter(), {
|
||||
stdin: { end: jest.fn() },
|
||||
stdout: new EventEmitter(),
|
||||
stderr: new EventEmitter(),
|
||||
});
|
||||
|
||||
execFileMock = jest.fn(() => executionStub);
|
||||
|
||||
di.override(nonPromiseExecFileInjectable, () => execFileMock as any);
|
||||
|
||||
execFileWithInput = di.inject(execFileWithInputInjectable);
|
||||
});
|
||||
|
||||
it("given call, when throws synchronously, resolves with failure", async () => {
|
||||
execFileMock.mockImplementation(() => {
|
||||
throw new Error("some-error");
|
||||
});
|
||||
|
||||
const actual = await execFileWithInput({
|
||||
filePath: "./irrelevant",
|
||||
commandArguments: ["irrelevant"],
|
||||
input: "irrelevant",
|
||||
});
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<AsyncResult<string, unknown>>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = execFileWithInput({
|
||||
filePath: "./some-file-path",
|
||||
commandArguments: ["some-arg", "some-other-arg"],
|
||||
input: "some-input",
|
||||
});
|
||||
});
|
||||
|
||||
it("calls for file with arguments", () => {
|
||||
expect(execFileMock).toHaveBeenCalledWith(
|
||||
"./some-file-path",
|
||||
[
|
||||
"some-arg",
|
||||
"some-other-arg",
|
||||
],
|
||||
{ "maxBuffer": 8589934592 },
|
||||
);
|
||||
});
|
||||
|
||||
it("calls with input", () => {
|
||||
expect(executionStub.stdin.end).toHaveBeenCalledWith("some-input");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stdout receives data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stdout.emit("data", "some-data");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stdout receives more data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stdout.emit("data", "some-other-data");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when execution exits with success, resolves with result", async () => {
|
||||
executionStub.emit("exit", 0);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: "some-datasome-other-data",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits without exit code, resolves with failure", async () => {
|
||||
executionStub.emit("exit", null, "SIGKILL");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Exited via SIGKILL",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits with failure, resolves with failure", async () => {
|
||||
executionStub.emit("exit", 42, "some-signal");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Failed with error: some-signal",
|
||||
});
|
||||
});
|
||||
|
||||
describe("when stderr receives data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stderr.emit("data", "some-error");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
describe("when stderr receives more data", () => {
|
||||
beforeEach(() => {
|
||||
executionStub.stderr.emit("data", "some-other-error");
|
||||
});
|
||||
|
||||
it("does not resolve yet", async () => {
|
||||
const promiseStatus = await getPromiseStatus(actualPromise);
|
||||
|
||||
expect(promiseStatus.fulfilled).toBe(false);
|
||||
});
|
||||
|
||||
it("when execution exits with success, resolves with result", async () => {
|
||||
executionStub.emit("exit", 0);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: true,
|
||||
response: "some-datasome-other-data",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits without exit code, resolves with failure", async () => {
|
||||
executionStub.emit("exit", null, "some-signal");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "Exited via some-signal",
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution exits with failure, resolves with errors", async () => {
|
||||
executionStub.emit("exit", 42, "irrelevant");
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: "some-errorsome-other-error",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("when execution receives error, resolves with error", async () => {
|
||||
executionStub.emit("error", new Error("some-error"));
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual({
|
||||
callWasSuccessful: false,
|
||||
error: expect.any(Error),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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 { execFile } from "child_process";
|
||||
|
||||
const nonPromiseExecFileInjectable = getInjectable({
|
||||
id: "non-promise-exec-file",
|
||||
instantiate: () => execFile,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
export default nonPromiseExecFileInjectable;
|
||||
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 callForKubeResourcesByManifestInjectable from "./call-for-kube-resources-by-manifest/call-for-kube-resources-by-manifest.injectable";
|
||||
import { groupBy, map } from "lodash/fp";
|
||||
import type { JsonObject } from "type-fest";
|
||||
import { pipeline } from "@ogre-tools/fp";
|
||||
import callForHelmManifestInjectable from "./call-for-helm-manifest/call-for-helm-manifest.injectable";
|
||||
|
||||
export type GetHelmReleaseResources = (
|
||||
name: string,
|
||||
namespace: string,
|
||||
kubeconfigPath: string,
|
||||
kubectlPath: string
|
||||
) => Promise<JsonObject[]>;
|
||||
|
||||
const getHelmReleaseResourcesInjectable = getInjectable({
|
||||
id: "get-helm-release-resources",
|
||||
|
||||
instantiate: (di): GetHelmReleaseResources => {
|
||||
const callForHelmManifest = di.inject(callForHelmManifestInjectable);
|
||||
const callForKubeResourcesByManifest = di.inject(callForKubeResourcesByManifestInjectable);
|
||||
|
||||
return async (name, namespace, kubeconfigPath, kubectlPath) => {
|
||||
const result = await callForHelmManifest(name, namespace, kubeconfigPath);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
const results = await pipeline(
|
||||
result.response,
|
||||
|
||||
groupBy((item) => item.metadata.namespace || namespace),
|
||||
|
||||
(x) => Object.entries(x),
|
||||
|
||||
map(([namespace, manifest]) =>
|
||||
callForKubeResourcesByManifest(namespace, kubeconfigPath, kubectlPath, manifest),
|
||||
),
|
||||
|
||||
promises => Promise.all(promises),
|
||||
);
|
||||
|
||||
return results.flat(1);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default getHelmReleaseResourcesInjectable;
|
||||
@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
|
||||
import type { GetHelmReleaseResources } from "./get-helm-release-resources.injectable";
|
||||
import getHelmReleaseResourcesInjectable from "./get-helm-release-resources.injectable";
|
||||
import type { ExecHelm } from "../../exec-helm/exec-helm.injectable";
|
||||
import execHelmInjectable from "../../exec-helm/exec-helm.injectable";
|
||||
import type { AsyncFnMock } from "@async-fn/jest";
|
||||
import asyncFn from "@async-fn/jest";
|
||||
import type { JsonObject } from "type-fest";
|
||||
import type { ExecFileWithInput } from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
|
||||
import execFileWithInputInjectable from "./call-for-kube-resources-by-manifest/exec-file-with-input/exec-file-with-input.injectable";
|
||||
|
||||
describe("get helm release resources", () => {
|
||||
let getHelmReleaseResources: GetHelmReleaseResources;
|
||||
let execHelmMock: AsyncFnMock<ExecHelm>;
|
||||
let execFileWithStreamInputMock: AsyncFnMock<ExecFileWithInput>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
execHelmMock = asyncFn();
|
||||
execFileWithStreamInputMock = asyncFn();
|
||||
|
||||
di.override(execHelmInjectable, () => execHelmMock);
|
||||
|
||||
di.override(
|
||||
execFileWithInputInjectable,
|
||||
() => execFileWithStreamInputMock,
|
||||
);
|
||||
|
||||
getHelmReleaseResources = di.inject(getHelmReleaseResourcesInjectable);
|
||||
});
|
||||
|
||||
describe("when called", () => {
|
||||
let actualPromise: Promise<JsonObject[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
actualPromise = getHelmReleaseResources(
|
||||
"some-release",
|
||||
"some-namespace",
|
||||
"/some-kubeconfig-path",
|
||||
"/some-kubectl-path",
|
||||
);
|
||||
});
|
||||
|
||||
it("calls for release manifest", () => {
|
||||
expect(execHelmMock).toHaveBeenCalledWith([
|
||||
"get", "manifest", "some-release", "--namespace", "some-namespace", "--kubeconfig", "/some-kubeconfig-path",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not call for resources yet", () => {
|
||||
expect(execFileWithStreamInputMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("when call for manifest resolves without resources, resolves without resources", async () => {
|
||||
await execHelmMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: "",
|
||||
});
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual([]);
|
||||
});
|
||||
|
||||
describe("when call for manifest resolves", () => {
|
||||
beforeEach(async () => {
|
||||
await execHelmMock.resolve({
|
||||
callWasSuccessful: true,
|
||||
response: `---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-same-namespace
|
||||
namespace: some-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-without-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-different-namespace
|
||||
namespace: some-other-namespace
|
||||
---
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it("calls for resources from each namespace separately using the manifest as input", () => {
|
||||
expect(execFileWithStreamInputMock.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: "/some-kubectl-path",
|
||||
commandArguments: ["get", "--kubeconfig", "/some-kubeconfig-path", "-f", "-", "--namespace", "some-namespace", "--output", "json"],
|
||||
input: `---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-same-namespace
|
||||
namespace: some-namespace
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-without-namespace
|
||||
---
|
||||
`,
|
||||
},
|
||||
],
|
||||
|
||||
[
|
||||
{
|
||||
filePath: "/some-kubectl-path",
|
||||
commandArguments: ["get", "--kubeconfig", "/some-kubeconfig-path", "-f", "-", "--namespace", "some-other-namespace", "--output", "json"],
|
||||
input: `---
|
||||
apiVersion: v1
|
||||
kind: SomeKind
|
||||
metadata:
|
||||
name: some-resource-with-different-namespace
|
||||
namespace: some-other-namespace
|
||||
---
|
||||
`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it("when all calls for resources resolve, resolves with combined result", async () => {
|
||||
await execFileWithStreamInputMock.resolveSpecific(
|
||||
([{ commandArguments }]) =>
|
||||
commandArguments.includes("some-namespace"),
|
||||
{
|
||||
callWasSuccessful: true,
|
||||
|
||||
response: JSON.stringify({
|
||||
items: [{ some: "item" }],
|
||||
|
||||
kind: "List",
|
||||
|
||||
metadata: {
|
||||
resourceVersion: "",
|
||||
selfLink: "",
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await execFileWithStreamInputMock.resolveSpecific(
|
||||
([{ commandArguments }]) =>
|
||||
commandArguments.includes("some-other-namespace"),
|
||||
{
|
||||
callWasSuccessful: true,
|
||||
|
||||
response: JSON.stringify({
|
||||
items: [{ some: "other-item" }],
|
||||
|
||||
kind: "List",
|
||||
|
||||
metadata: {
|
||||
resourceVersion: "",
|
||||
selfLink: "",
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const actual = await actualPromise;
|
||||
|
||||
expect(actual).toEqual([{ some: "item" }, { some: "other-item" }]);
|
||||
});
|
||||
|
||||
it("given some call fails, when all calls have finished, rejects with failure", async () => {
|
||||
await execFileWithStreamInputMock.resolveSpecific(
|
||||
([{ commandArguments }]) =>
|
||||
commandArguments.includes("some-namespace"),
|
||||
|
||||
{
|
||||
callWasSuccessful: true,
|
||||
|
||||
response: JSON.stringify({
|
||||
items: [{ some: "item" }],
|
||||
|
||||
kind: "List",
|
||||
|
||||
metadata: {
|
||||
resourceVersion: "",
|
||||
selfLink: "",
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
execFileWithStreamInputMock.resolveSpecific(
|
||||
([{ commandArguments }]) =>
|
||||
commandArguments.includes("some-other-namespace"),
|
||||
|
||||
{
|
||||
callWasSuccessful: false,
|
||||
error: "some-error",
|
||||
},
|
||||
);
|
||||
|
||||
return expect(actualPromise).rejects.toEqual(expect.any(Error));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import getHelmReleaseInjectable from "./get-helm-release.injectable";
|
||||
|
||||
export default getGlobalOverride(getHelmReleaseInjectable, () => () => {
|
||||
throw new Error("Tried to get helm release without explicit override");
|
||||
});
|
||||
@ -4,14 +4,18 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Cluster } from "../../../common/cluster/cluster";
|
||||
import { getRelease } from "../helm-release-manager";
|
||||
import loggerInjectable from "../../../common/logger.injectable";
|
||||
import { isObject, json } from "../../../common/utils";
|
||||
import execHelmInjectable from "../exec-helm/exec-helm.injectable";
|
||||
import getHelmReleaseResourcesInjectable from "./get-helm-release-resources/get-helm-release-resources.injectable";
|
||||
|
||||
const getHelmReleaseInjectable = getInjectable({
|
||||
id: "get-helm-release",
|
||||
|
||||
instantiate: (di) => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const execHelm = di.inject(execHelmInjectable);
|
||||
const getHelmReleaseResources = di.inject(getHelmReleaseResourcesInjectable);
|
||||
|
||||
return async (cluster: Cluster, releaseName: string, namespace: string) => {
|
||||
const kubeconfigPath = await cluster.getProxyKubeconfigPath();
|
||||
@ -20,7 +24,37 @@ const getHelmReleaseInjectable = getInjectable({
|
||||
|
||||
logger.debug("Fetch release");
|
||||
|
||||
return getRelease(releaseName, namespace, kubeconfigPath, kubectlPath);
|
||||
const args = [
|
||||
"status",
|
||||
releaseName,
|
||||
"--namespace",
|
||||
namespace,
|
||||
"--kubeconfig",
|
||||
kubeconfigPath,
|
||||
"--output",
|
||||
"json",
|
||||
];
|
||||
|
||||
const result = await execHelm(args);
|
||||
|
||||
if (!result.callWasSuccessful) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const release = json.parse(result.response);
|
||||
|
||||
if (!isObject(release) || Array.isArray(release)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
release.resources = await getHelmReleaseResources(
|
||||
releaseName,
|
||||
namespace,
|
||||
kubeconfigPath,
|
||||
kubectlPath,
|
||||
);
|
||||
|
||||
return release;
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getGlobalOverride } from "../../../common/test-utils/get-global-override";
|
||||
import updateHelmReleaseInjectable from "./update-helm-release.injectable";
|
||||
|
||||
export default getGlobalOverride(updateHelmReleaseInjectable, () => () => {
|
||||
throw new Error("Tried to update helm release without explicit override");
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user