1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Refactor ContainerEnviroment into seperate components

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-06-20 10:55:13 -04:00
parent 1e739ba7db
commit 4a37a74420
9 changed files with 327 additions and 215 deletions

View File

@ -100,6 +100,30 @@ export interface VolumeMount {
subPathExpr?: string;
}
export interface EnvVar {
name: string;
value?: string;
valueFrom?: {
fieldRef?: {
apiVersion: string;
fieldPath: string;
};
secretKeyRef?: {
key: string;
name: string;
};
configMapKeyRef?: {
key: string;
name: string;
};
};
}
export interface EnvFromSource {
configMapRef?: LocalObjectReference;
secretRef?: LocalObjectReference;
}
export interface PodContainer extends Partial<Record<PodContainerProbe, IContainerProbe>> {
name: string;
image: string;
@ -118,28 +142,8 @@ export interface PodContainer extends Partial<Record<PodContainerProbe, IContain
};
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;
}[];
env?: EnvVar[];
envFrom?: EnvFromSource[];
volumeMounts?: VolumeMount[];
imagePullPolicy?: string;
}

View File

@ -1,183 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./pod-container-env.scss";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import type { PodContainer } from "../../../common/k8s-api/endpoints";
import { DrawerItem } from "../drawer";
import { autorun } from "mobx";
import { iter } from "../../utils";
import _ from "lodash";
import type { SecretStore } from "../+config-secrets/store";
import type { ConfigMapStore } from "../+config-maps/store";
import { withInjectables } from "@ogre-tools/injectable-react";
import configMapStoreInjectable from "../+config-maps/store.injectable";
import secretStoreInjectable from "../+config-secrets/store.injectable";
import { SecretKey } from "../+config-secrets/secret-key";
export interface ContainerEnvironmentProps {
container: PodContainer;
namespace: string;
}
interface Dependencies {
secretStore: SecretStore;
configMapStore: ConfigMapStore;
}
const NonInjectedContainerEnvironment = observer((props: ContainerEnvironmentProps & Dependencies) => {
const {
container: { env, envFrom },
namespace,
configMapStore,
secretStore,
} = props;
useEffect(() => autorun(() => {
for (const { valueFrom } of env ?? []) {
if (valueFrom?.configMapKeyRef) {
configMapStore.load({ name: valueFrom.configMapKeyRef.name, namespace });
}
}
for (const { configMapRef, secretRef } of envFrom ?? []) {
if (secretRef?.name) {
secretStore.load({ name: secretRef.name, namespace });
}
if (configMapRef?.name) {
configMapStore.load({ name: configMapRef.name, namespace });
}
}
}), []);
const renderEnv = () => {
const orderedEnv = _.sortBy(env, "name");
return orderedEnv.map(variable => {
const { name, value, valueFrom } = variable;
let secretValue = null;
if (value) {
secretValue = value;
}
if (valueFrom) {
const { fieldRef, secretKeyRef, configMapKeyRef } = valueFrom;
if (fieldRef) {
const { apiVersion, fieldPath } = fieldRef;
secretValue = `fieldRef(${apiVersion}:${fieldPath})`;
}
if (secretKeyRef) {
const secret = secretStore.getByName(secretKeyRef.name, namespace);
if (secret) {
secretValue = (
<SecretKey
secret={secret}
field={secretKeyRef.key}
/>
);
}
}
if (configMapKeyRef) {
const { name, key } = configMapKeyRef;
const configMap = configMapStore.getByName(name, namespace);
secretValue = configMap
? configMap.data[key]
: `configMapKeyRef(${name}${key})`;
}
}
return (
<div
className="variable"
key={name}
data-testid={`env-${name}`}
>
<span className="var-name">{name}</span>
{`= `}
{secretValue}
</div>
);
});
};
const renderEnvFrom = () => {
return Array.from(iter.filterFlatMap(envFrom ?? [], vars => {
if (vars.configMapRef?.name) {
return renderEnvFromConfigMap(vars.configMapRef.name);
}
if (vars.secretRef?.name) {
return renderEnvFromSecret(vars.secretRef.name);
}
return null;
}));
};
const renderEnvFromConfigMap = (configMapName: string) => {
const configMap = configMapStore.getByName(configMapName, namespace);
if (!configMap) return null;
return Object.entries(configMap.data).map(([name, value]) => (
<div
className="variable"
key={name}
data-testid={`envFrom-configmap-${configMap.getName()}`}
>
<span className="var-name">{name}</span>
{`= `}
{value}
</div>
));
};
const renderEnvFromSecret = (secretName: string) => {
const secret = secretStore.getByName(secretName, namespace);
if (!secret) return null;
return Object.keys(secret.data)
.map(name => (
<div
className="variable"
key={name}
data-testid={`envFrom-secret-${secretName}-${name}`}
>
<span className="var-name">{name}</span>
{`= `}
<SecretKey
secret={secret}
field={name}
/>
</div>
));
};
return (
<DrawerItem name="Environment" className="ContainerEnvironment">
{renderEnv()}
{renderEnvFrom()}
</DrawerItem>
);
});
export const ContainerEnvironment = withInjectables<Dependencies, ContainerEnvironmentProps>(NonInjectedContainerEnvironment, {
getProps: (di, props) => ({
...props,
configMapStore: di.inject(configMapStoreInjectable),
secretStore: di.inject(secretStoreInjectable),
}),
});

View File

@ -0,0 +1,120 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { autorun } from "mobx";
import React, { useEffect } from "react";
import type { ConfigMapStore } from "../../+config-maps/store";
import configMapStoreInjectable from "../../+config-maps/store.injectable";
import { SecretKey } from "../../+config-secrets/secret-key";
import type { SecretStore } from "../../+config-secrets/store";
import secretStoreInjectable from "../../+config-secrets/store.injectable";
import type { EnvFromSource } from "../../../../common/k8s-api/endpoints";
import type { Logger } from "../../../../common/logger";
import loggerInjectable from "../../../../common/logger.injectable";
import { iter, object } from "../../../utils";
export interface ContainerEnvFromSourceProps {
envFrom: EnvFromSource[];
namespace: string;
}
interface Dependencies {
secretStore: SecretStore;
configMapStore: ConfigMapStore;
logger: Logger;
}
const NonInjectedContainerEnvFromSource = ({
envFrom,
namespace,
configMapStore,
secretStore,
logger,
}: ContainerEnvFromSourceProps & Dependencies) => {
useEffect(() => autorun(() => {
for (const { configMapRef, secretRef } of envFrom) {
if (secretRef?.name) {
secretStore
.load({ name: secretRef.name, namespace })
.catch(error => logger.warn(`[CONTAINER-ENV]: failed to load Secret ${secretRef.name} in ns=${namespace}`, error));
}
if (configMapRef?.name) {
configMapStore
.load({ name: configMapRef.name, namespace })
.catch(error => logger.warn(`[CONTAINER-ENV]: failed to load ConfigMap ${configMapRef.name} in ns=${namespace}`, error));
}
}
}), []);
const renderValue = (testIdName: string, fieldName: string, kind: "configmap" | "secret", value: JSX.Element | string) => (
<div
className="variable"
key={fieldName}
data-testid={`envFrom-${kind}-${testIdName}`}
>
<span className="var-name">{fieldName}</span>
{`= `}
{value}
</div>
);
const renderEnvFromConfigMap = (configMapName: string) => {
const configMap = configMapStore.getByName(configMapName, namespace);
return object.entries(configMap?.data ?? {})
.map(([fieldName, value]) => renderValue(
`${configMapName}:${fieldName}`,
fieldName,
"configmap",
value,
));
};
const renderEnvFromSecret = (secretName: string) => {
const secret = secretStore.getByName(secretName, namespace);
if (!secret) return null;
return Object.keys(secret.data)
.map(fieldName => renderValue(
`${secretName}:${fieldName}`,
fieldName,
"secret",
<SecretKey
secret={secret}
field={fieldName}
/>,
));
};
return (
<>
{
Array.from(iter.filterFlatMap(envFrom, vars => {
if (vars.configMapRef?.name) {
return renderEnvFromConfigMap(vars.configMapRef.name);
}
if (vars.secretRef?.name) {
return renderEnvFromSecret(vars.secretRef.name);
}
return null;
}))
}
</>
);
};
export const ContainerEnvFromSource = withInjectables<Dependencies, ContainerEnvFromSourceProps>(NonInjectedContainerEnvFromSource, {
getProps: (di, props) => ({
...props,
configMapStore: di.inject(configMapStoreInjectable),
secretStore: di.inject(secretStoreInjectable),
logger: di.inject(loggerInjectable),
}),
});

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { withInjectables } from "@ogre-tools/injectable-react";
import { sortBy } from "lodash";
import { autorun } from "mobx";
import React, { useEffect } from "react";
import type { ConfigMapStore } from "../../+config-maps/store";
import configMapStoreInjectable from "../../+config-maps/store.injectable";
import { SecretKey } from "../../+config-secrets/secret-key";
import type { SecretStore } from "../../+config-secrets/store";
import secretStoreInjectable from "../../+config-secrets/store.injectable";
import type { EnvVar } from "../../../../common/k8s-api/endpoints";
import type { Logger } from "../../../../common/logger";
import loggerInjectable from "../../../../common/logger.injectable";
export interface ContainerEnvProps {
env: EnvVar[];
namespace: string;
}
interface Dependencies {
secretStore: SecretStore;
configMapStore: ConfigMapStore;
logger: Logger;
}
const NonInjectedContainerEnv = ({
env,
namespace,
configMapStore,
secretStore,
logger,
}: ContainerEnvProps & Dependencies) => {
useEffect(() => autorun(() => {
for (const { valueFrom } of env) {
if (valueFrom?.configMapKeyRef) {
const { configMapKeyRef } = valueFrom;
configMapStore
.load({ name: configMapKeyRef.name, namespace })
.catch(error => logger.warn(`[CONTAINER-ENV]: failed to load ConfigMap ${configMapKeyRef.name} in ns=${namespace}`, error));
}
if (valueFrom?.secretKeyRef) {
const { secretKeyRef } = valueFrom;
secretStore
.load({ name: secretKeyRef.name, namespace })
.catch(error => logger.warn(`[CONTAINER-ENV]: failed to load Secret ${secretKeyRef.name} in ns=${namespace}`, error));
}
}
}), []);
const getEnvVarValue = ({ value, valueFrom }: EnvVar) => {
if (value) {
return value;
}
if (!valueFrom) {
return null;
}
const { fieldRef, secretKeyRef, configMapKeyRef } = valueFrom;
if (fieldRef) {
const { apiVersion, fieldPath } = fieldRef;
return `fieldRef(${apiVersion}:${fieldPath})`;
}
if (secretKeyRef) {
const secret = secretStore.getByName(secretKeyRef.name, namespace);
if (secret) {
return (
<SecretKey
secret={secret}
field={secretKeyRef.key}
/>
);
}
}
if (configMapKeyRef) {
const { name, key } = configMapKeyRef;
const configMap = configMapStore.getByName(name, namespace);
return configMap?.data[key] ?? `configMapKeyRef(${name}${key})`;
}
return null;
};
return (
<>
{
sortBy(env, "name")
.map(envVar => ({
name: envVar.name,
value: getEnvVarValue(envVar),
}))
.filter(({ value }) => value !== null)
.map(({ name, value }) => (
<div
className="variable"
key={name}
data-testid={`env-${name}`}
>
<span className="var-name">{name}</span>
{`= `}
{value}
</div>
))
}
</>
);
};
export const ContainerEnv = withInjectables<Dependencies, ContainerEnvProps>(NonInjectedContainerEnv, {
getProps: (di, props) => ({
...props,
configMapStore: di.inject(configMapStoreInjectable),
secretStore: di.inject(secretStoreInjectable),
logger: di.inject(loggerInjectable),
}),
});

View File

@ -13,7 +13,7 @@ import { Secret, ConfigMap, Pod, SecretType } from "../../../../common/k8s-api/e
import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import { ContainerEnvironment } from "../pod-container-env";
import { ContainerEnvironment } from "./view";
describe("<ContainerEnv />", () => {
let render: DiRender;
@ -24,11 +24,15 @@ describe("<ContainerEnv />", () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
secretStore = ({
load: jest.fn(),
load: jest.fn().mockImplementation(async () => {
return {} as Secret;
}),
getByName: jest.fn(),
});
configMapStore = ({
load: jest.fn(),
load: jest.fn().mockImplementation(async () => {
return {} as ConfigMap;
}),
getByName: jest.fn(),
});
@ -87,7 +91,7 @@ describe("<ContainerEnv />", () => {
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.getByTestId("env-foobar").innerHTML).toBe(`<span class="var-name">foobar</span>= https://localhost:12345`);
expect(result.getByTestId("env-foobar")).toHaveTextContent(`foobar= https://localhost:12345`);
});
it("renders envFrom when given a configMapRef", () => {
@ -136,7 +140,7 @@ describe("<ContainerEnv />", () => {
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.getByTestId("envFrom-configmap-my-config-map").innerHTML).toBe(`<span class="var-name">configFoo</span>= configBar`);
expect(result.getByTestId("envFrom-configmap-my-config-map:configFoo")).toHaveTextContent(`configFoo= configBar`);
});
it("renders envFrom when given a secretRef", () => {
@ -186,7 +190,7 @@ describe("<ContainerEnv />", () => {
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.getByTestId("envFrom-secret-my-secret-bar").innerHTML).toMatch(`<span class="var-name">bar</span>= secretKeyRef(my-secret.bar)`);
expect(result.getByTestId("envFrom-secret-my-secret:bar")).toHaveTextContent(/^bar= secretKeyRef\(my-secret\.bar\)/);
});
it("renders env", () => {
@ -214,7 +218,7 @@ describe("<ContainerEnv />", () => {
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.getByTestId("env-foobar").innerHTML).toBe(`<span class="var-name">foobar</span>= https://localhost:12345`);
expect(result.getByTestId("env-foobar")).toHaveTextContent(`foobar= https://localhost:12345`);
});
it("renders both env and configMapRef envFrom", () => {
@ -267,7 +271,7 @@ describe("<ContainerEnv />", () => {
});
const result = render(<ContainerEnvironment container={container} namespace={pod.getNs()} />);
expect(result.getByTestId("env-foobar").innerHTML).toBe(`<span class="var-name">foobar</span>= https://localhost:12345`);
expect(result.getByTestId("envFrom-configmap-my-config-map").innerHTML).toBe(`<span class="var-name">configFoo</span>= configBar`);
expect(result.getByTestId("env-foobar")).toHaveTextContent(`foobar= https://localhost:12345`);
expect(result.getByTestId("envFrom-configmap-my-config-map:configFoo")).toHaveTextContent(`configFoo= configBar`);
});
});

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./view.scss";
import React from "react";
import { observer } from "mobx-react";
import type { PodContainer } from "../../../../common/k8s-api/endpoints";
import { DrawerItem } from "../../drawer";
import { ContainerEnv } from "./env";
import { ContainerEnvFromSource } from "./env-from";
export interface ContainerEnvironmentProps {
container: PodContainer;
namespace: string;
}
export const ContainerEnvironment = observer(({
container: { env, envFrom },
namespace,
}: ContainerEnvironmentProps) => (
<DrawerItem name="Environment" className="ContainerEnvironment">
{env && (
<ContainerEnv
env={env}
namespace={namespace}
/>
)}
{envFrom && (
<ContainerEnvFromSource
envFrom={envFrom}
namespace={namespace}
/>
)}
</DrawerItem>
));

View File

@ -11,7 +11,7 @@ import { DrawerItem } from "../drawer";
import { cssNames, isDefined } from "../../utils";
import { StatusBrick } from "../status-brick";
import { Badge } from "../badge";
import { ContainerEnvironment } from "./pod-container-env";
import { ContainerEnvironment } from "./pod-container-env/view";
import { PodContainerPort } from "./pod-container-port";
import { ResourceMetrics } from "../resource-metrics";
import type { MetricData } from "../../../common/k8s-api/endpoints/metrics.api";