From 4a37a744208dedc025f71002ae3f2b2270a92829 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 20 Jun 2022 10:55:13 -0400 Subject: [PATCH] Refactor ContainerEnviroment into seperate components Signed-off-by: Sebastian Malton --- src/common/k8s-api/endpoints/pod.api.ts | 48 ++--- .../+workloads-pods/pod-container-env.tsx | 183 ------------------ .../__snapshots__/view.test.tsx.snap} | 0 .../pod-container-env/env-from.tsx | 120 ++++++++++++ .../+workloads-pods/pod-container-env/env.tsx | 129 ++++++++++++ .../view.scss} | 0 .../view.test.tsx} | 22 ++- .../pod-container-env/view.tsx | 38 ++++ .../+workloads-pods/pod-details-container.tsx | 2 +- 9 files changed, 327 insertions(+), 215 deletions(-) delete mode 100644 src/renderer/components/+workloads-pods/pod-container-env.tsx rename src/renderer/components/+workloads-pods/{__tests__/__snapshots__/pod-container-env.test.tsx.snap => pod-container-env/__snapshots__/view.test.tsx.snap} (100%) create mode 100644 src/renderer/components/+workloads-pods/pod-container-env/env-from.tsx create mode 100644 src/renderer/components/+workloads-pods/pod-container-env/env.tsx rename src/renderer/components/+workloads-pods/{pod-container-env.scss => pod-container-env/view.scss} (100%) rename src/renderer/components/+workloads-pods/{__tests__/pod-container-env.test.tsx => pod-container-env/view.test.tsx} (88%) create mode 100644 src/renderer/components/+workloads-pods/pod-container-env/view.tsx diff --git a/src/common/k8s-api/endpoints/pod.api.ts b/src/common/k8s-api/endpoints/pod.api.ts index 4ac49489c9..d60f8cbb2f 100644 --- a/src/common/k8s-api/endpoints/pod.api.ts +++ b/src/common/k8s-api/endpoints/pod.api.ts @@ -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> { name: string; image: string; @@ -118,28 +142,8 @@ export interface PodContainer extends Partial { - 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 = ( - - ); - } - } - - if (configMapKeyRef) { - const { name, key } = configMapKeyRef; - const configMap = configMapStore.getByName(name, namespace); - - secretValue = configMap - ? configMap.data[key] - : `configMapKeyRef(${name}${key})`; - } - } - - return ( -
- {name} - {`= `} - {secretValue} -
- ); - }); - }; - - 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]) => ( -
- {name} - {`= `} - {value} -
- )); - }; - - const renderEnvFromSecret = (secretName: string) => { - const secret = secretStore.getByName(secretName, namespace); - - if (!secret) return null; - - return Object.keys(secret.data) - .map(name => ( -
- {name} - {`= `} - -
- )); - }; - - return ( - - {renderEnv()} - {renderEnvFrom()} - - ); -}); - -export const ContainerEnvironment = withInjectables(NonInjectedContainerEnvironment, { - getProps: (di, props) => ({ - ...props, - configMapStore: di.inject(configMapStoreInjectable), - secretStore: di.inject(secretStoreInjectable), - }), -}); diff --git a/src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap b/src/renderer/components/+workloads-pods/pod-container-env/__snapshots__/view.test.tsx.snap similarity index 100% rename from src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap rename to src/renderer/components/+workloads-pods/pod-container-env/__snapshots__/view.test.tsx.snap diff --git a/src/renderer/components/+workloads-pods/pod-container-env/env-from.tsx b/src/renderer/components/+workloads-pods/pod-container-env/env-from.tsx new file mode 100644 index 0000000000..58f5c5f59f --- /dev/null +++ b/src/renderer/components/+workloads-pods/pod-container-env/env-from.tsx @@ -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) => ( +
+ {fieldName} + {`= `} + {value} +
+ ); + + 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", + , + )); + }; + + 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(NonInjectedContainerEnvFromSource, { + getProps: (di, props) => ({ + ...props, + configMapStore: di.inject(configMapStoreInjectable), + secretStore: di.inject(secretStoreInjectable), + logger: di.inject(loggerInjectable), + }), +}); diff --git a/src/renderer/components/+workloads-pods/pod-container-env/env.tsx b/src/renderer/components/+workloads-pods/pod-container-env/env.tsx new file mode 100644 index 0000000000..b905946220 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pod-container-env/env.tsx @@ -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 ( + + ); + } + } + + 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 }) => ( +
+ {name} + {`= `} + {value} +
+ )) + } + + ); +}; + +export const ContainerEnv = withInjectables(NonInjectedContainerEnv, { + getProps: (di, props) => ({ + ...props, + configMapStore: di.inject(configMapStoreInjectable), + secretStore: di.inject(secretStoreInjectable), + logger: di.inject(loggerInjectable), + }), +}); diff --git a/src/renderer/components/+workloads-pods/pod-container-env.scss b/src/renderer/components/+workloads-pods/pod-container-env/view.scss similarity index 100% rename from src/renderer/components/+workloads-pods/pod-container-env.scss rename to src/renderer/components/+workloads-pods/pod-container-env/view.scss diff --git a/src/renderer/components/+workloads-pods/__tests__/pod-container-env.test.tsx b/src/renderer/components/+workloads-pods/pod-container-env/view.test.tsx similarity index 88% rename from src/renderer/components/+workloads-pods/__tests__/pod-container-env.test.tsx rename to src/renderer/components/+workloads-pods/pod-container-env/view.test.tsx index 2e054ab760..819806cb57 100644 --- a/src/renderer/components/+workloads-pods/__tests__/pod-container-env.test.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-env/view.test.tsx @@ -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("", () => { let render: DiRender; @@ -24,11 +24,15 @@ describe("", () => { 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("", () => { }); const result = render(); - expect(result.getByTestId("env-foobar").innerHTML).toBe(`foobar= 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("", () => { }); const result = render(); - expect(result.getByTestId("envFrom-configmap-my-config-map").innerHTML).toBe(`configFoo= 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("", () => { }); const result = render(); - expect(result.getByTestId("envFrom-secret-my-secret-bar").innerHTML).toMatch(`bar= 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("", () => { }); const result = render(); - expect(result.getByTestId("env-foobar").innerHTML).toBe(`foobar= 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("", () => { }); const result = render(); - expect(result.getByTestId("env-foobar").innerHTML).toBe(`foobar= https://localhost:12345`); - expect(result.getByTestId("envFrom-configmap-my-config-map").innerHTML).toBe(`configFoo= configBar`); + expect(result.getByTestId("env-foobar")).toHaveTextContent(`foobar= https://localhost:12345`); + expect(result.getByTestId("envFrom-configmap-my-config-map:configFoo")).toHaveTextContent(`configFoo= configBar`); }); }); diff --git a/src/renderer/components/+workloads-pods/pod-container-env/view.tsx b/src/renderer/components/+workloads-pods/pod-container-env/view.tsx new file mode 100644 index 0000000000..b0287d08d2 --- /dev/null +++ b/src/renderer/components/+workloads-pods/pod-container-env/view.tsx @@ -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) => ( + + {env && ( + + )} + {envFrom && ( + + )} + +)); diff --git a/src/renderer/components/+workloads-pods/pod-details-container.tsx b/src/renderer/components/+workloads-pods/pod-details-container.tsx index 0b0abb8eb3..b91fb8eb11 100644 --- a/src/renderer/components/+workloads-pods/pod-details-container.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-container.tsx @@ -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";