diff --git a/packages/core/src/renderer/components/+workloads-pods/__snapshots__/secret-key.test.tsx.snap b/packages/core/src/renderer/components/+workloads-pods/__snapshots__/secret-key.test.tsx.snap
new file mode 100644
index 0000000000..3d1c8368ad
--- /dev/null
+++ b/packages/core/src/renderer/components/+workloads-pods/__snapshots__/secret-key.test.tsx.snap
@@ -0,0 +1,92 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`SecretKey technical tests renders 1`] = `
+
+
+ secret(some-secret-name)[some-key]
+
+
+
+ visibility
+
+
+
+ Show
+
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked renders 1`] = `
+
+
+ secret(some-secret-name)[some-key]
+
+
+
+ visibility
+
+
+
+ Show
+
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with a primitive renders 1`] = `
+
+
+ Error: some-other-error
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with an error renders 1`] = `
+
+
+ Error: some-error
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked when the secret fails to load with an object renders 1`] = `
+
+
+ Error: {"message":"some-error"}
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked when the secret loads with base64 encoded data renders 1`] = `
+
+
+ some-data-for-some-key
+
+
+`;
+
+exports[`SecretKey technical tests when the show secret button is clicked when the secret loads with non base64 encoded data renders 1`] = `
+
+
+ some-data-for-some-key
+
+
+`;
diff --git a/packages/core/src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap b/packages/core/src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap
index 1183ce4309..1a243d6fa8 100644
--- a/packages/core/src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap
+++ b/packages/core/src/renderer/components/+workloads-pods/__tests__/__snapshots__/pod-container-env.test.tsx.snap
@@ -158,10 +158,11 @@ exports[` renders envFrom when given a secretRef 1`] = `
bar
:
- secretKeyRef(my-secret.bar)
+ secret(my-secret)[bar]
renders envFrom when given a secretRef 1`] = `
visibility
-
diff --git a/packages/core/src/renderer/components/+workloads-pods/pod-container-env.tsx b/packages/core/src/renderer/components/+workloads-pods/pod-container-env.tsx
index 977c945f18..00d13fa833 100644
--- a/packages/core/src/renderer/components/+workloads-pods/pod-container-env.tsx
+++ b/packages/core/src/renderer/components/+workloads-pods/pod-container-env.tsx
@@ -5,19 +5,19 @@
import "./pod-container-env.scss";
-import React, { useEffect, useState } from "react";
+import React, { useEffect } from "react";
import { observer } from "mobx-react";
-import type { Container, EnvVarKeySelector, Secret } from "../../../common/k8s-api/endpoints";
+import type { Container } from "../../../common/k8s-api/endpoints";
import { DrawerItem } from "../drawer";
import { autorun } from "mobx";
-import { Icon } from "../icon";
-import { base64, cssNames, object } from "@k8slens/utilities";
+import { object } from "@k8slens/utilities";
import _ from "lodash";
import { withInjectables } from "@ogre-tools/injectable-react";
import type { ConfigMapStore } from "../+config-maps/store";
import type { SecretStore } from "../+config-secrets/store";
import configMapStoreInjectable from "../+config-maps/store.injectable";
import secretStoreInjectable from "../+config-secrets/store.injectable";
+import { SecretKey } from "./secret-key";
export interface ContainerEnvironmentProps {
container: Container;
@@ -74,9 +74,11 @@ const NonInjectedContainerEnvironment = observer((props: Dependencies & Containe
} else if (secretKeyRef?.name) {
secretValue = (
);
} else if (configMapKeyRef?.name) {
@@ -151,7 +153,6 @@ const NonInjectedContainerEnvironment = observer((props: Dependencies & Containe
key,
}}
namespace={namespace}
- secretStore={secretStore}
/>
));
@@ -172,52 +173,3 @@ export const ContainerEnvironment = withInjectables {
- const {
- reference: { name, key },
- namespace,
- secretStore,
- } = props;
-
- const [loading, setLoading] = useState(false);
- const [secret, setSecret] = useState();
-
- if (!name) {
- return null;
- }
-
- const showKey = async (evt: React.MouseEvent) => {
- evt.preventDefault();
- setLoading(true);
- const secret = await secretStore.load({ name, namespace });
-
- setLoading(false);
- setSecret(secret);
- };
-
- const value = secret?.data?.[key];
-
- if (value) {
- return <>{base64.decode(value)}>;
- }
-
- return (
- <>
- {`secretKeyRef(${name}.${key})`}
-
-
- >
- );
-};
diff --git a/packages/core/src/renderer/components/+workloads-pods/secret-key.test.tsx b/packages/core/src/renderer/components/+workloads-pods/secret-key.test.tsx
new file mode 100644
index 0000000000..035bf158df
--- /dev/null
+++ b/packages/core/src/renderer/components/+workloads-pods/secret-key.test.tsx
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+
+import type { AsyncFnMock } from "@async-fn/jest";
+import asyncFn from "@async-fn/jest";
+import { base64 } from "@k8slens/utilities";
+import type { RenderResult } from "@testing-library/react";
+import { act } from "@testing-library/react";
+import React from "react";
+import type { SecretStore } from "../+config-secrets/store";
+import secretStoreInjectable from "../+config-secrets/store.injectable";
+import { Secret, SecretType } from "../../../common/k8s-api/endpoints";
+import { getDiForUnitTesting } from "../../getDiForUnitTesting";
+import { renderFor } from "../test-utils/renderFor";
+import { SecretKey } from "./secret-key";
+
+describe("SecretKey technical tests", () => {
+ let loadSecretMock: AsyncFnMock;
+ let result: RenderResult;
+
+ beforeEach(() => {
+ const di = getDiForUnitTesting();
+ const render = renderFor(di);
+
+ loadSecretMock = asyncFn();
+ di.override(secretStoreInjectable, () => ({
+ load: loadSecretMock,
+ } as Partial as SecretStore));
+
+ result = render((
+
+ ));
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not try to load secret", () => {
+ expect(loadSecretMock).not.toBeCalled();
+ });
+
+ it("should show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).toBeInTheDocument();
+ });
+
+ describe("when the show secret button is clicked", () => {
+ beforeEach(() => {
+ result
+ .getByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")
+ .click();
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should try to load secret", () => {
+ expect(loadSecretMock).toBeCalledWith({
+ name: "some-secret-name",
+ namespace: "some-namespace",
+ });
+ });
+
+ it("should mark icon as disabled", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).toHaveClass("disabled");
+ });
+
+ describe("when the secret loads with base64 encoded data", () => {
+ beforeEach(async () => {
+ await act(async () => {
+ await loadSecretMock.resolve(new Secret({
+ apiVersion: Secret.apiBase,
+ kind: Secret.kind,
+ metadata: {
+ name: "some-secret-name",
+ namespace: "some-namespace",
+ resourceVersion: "some-resource-version",
+ selfLink: "some-self-link",
+ uid: "some-uid",
+ },
+ type: SecretType.Opaque,
+ data: {
+ "some-key": base64.encode("some-data-for-some-key"),
+ },
+ }));
+ });
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
+ });
+
+ it("should show the decoded secret data", () => {
+ expect(result.queryByText("some-data-for-some-key")).toBeInTheDocument();
+ });
+ });
+
+ describe("when the secret loads with non base64 encoded data", () => {
+ beforeEach(async () => {
+ await act(async () => {
+ await loadSecretMock.resolve(new Secret({
+ apiVersion: Secret.apiBase,
+ kind: Secret.kind,
+ metadata: {
+ name: "some-secret-name",
+ namespace: "some-namespace",
+ resourceVersion: "some-resource-version",
+ selfLink: "some-self-link",
+ uid: "some-uid",
+ },
+ type: SecretType.Opaque,
+ data: {
+ "some-key": "some-data-for-some-key",
+ },
+ }));
+ });
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
+ });
+
+ it("should show the non decoded secret data", () => {
+ expect(result.queryByText("some-data-for-some-key")).toBeInTheDocument();
+ });
+ });
+
+ describe("when the secret fails to load with an error", () => {
+ beforeEach(async () => {
+ await act(async () => {
+ await loadSecretMock.reject(new Error("some-error"));
+ });
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
+ });
+
+ it("should show the loading error", () => {
+ expect(result.queryByText("Error: some-error")).toBeInTheDocument();
+ });
+ });
+
+ describe("when the secret fails to load with an object", () => {
+ beforeEach(async () => {
+ await act(async () => {
+ await loadSecretMock.reject({ message: "some-error" });
+ });
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
+ });
+
+ it("should show the loading error as JSON", () => {
+ expect(result.queryByText(`Error: {"message":"some-error"}`)).toBeInTheDocument();
+ });
+ });
+
+ describe("when the secret fails to load with a primitive", () => {
+ beforeEach(async () => {
+ await act(async () => {
+ await loadSecretMock.reject("some-other-error");
+ });
+ });
+
+ it("renders", () => {
+ expect(result.baseElement).toMatchSnapshot();
+ });
+
+ it("should not show the 'show secret' button", () => {
+ expect(result.queryByTestId("show-secret-button-for-some-namespace/some-secret-name:some-key")).not.toBeInTheDocument();
+ });
+
+ it("should show the loading error as JSON", () => {
+ expect(result.queryByText("Error: some-other-error")).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/packages/core/src/renderer/components/+workloads-pods/secret-key.tsx b/packages/core/src/renderer/components/+workloads-pods/secret-key.tsx
new file mode 100644
index 0000000000..ada6e9c3a2
--- /dev/null
+++ b/packages/core/src/renderer/components/+workloads-pods/secret-key.tsx
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) OpenLens Authors. All rights reserved.
+ * Licensed under MIT License. See LICENSE in root directory for more information.
+ */
+import React, { useState } from "react";
+import type { EnvVarKeySelector } from "../../../common/k8s-api/endpoints";
+import { Icon } from "../icon";
+import { base64, cssNames, isObject } from "@k8slens/utilities";
+import type { SecretStore } from "../+config-secrets/store";
+import { withInjectables } from "@ogre-tools/injectable-react";
+import secretStoreInjectable from "../+config-secrets/store.injectable";
+import type { SetRequired } from "type-fest";
+
+export interface SecretKeyProps {
+ reference: SetRequired;
+ namespace: string;
+}
+
+interface Dependencies {
+ secretStore: SecretStore;
+}
+
+const NonInjectedSecretKey = (props: SecretKeyProps & Dependencies) => {
+ const {
+ reference: { name, key }, namespace, secretStore,
+ } = props;
+
+ const [loading, setLoading] = useState(false);
+ const [secretData, setSecretData] = useState();
+
+ if (!name) {
+ return null;
+ }
+
+ const showKey = async (evt: React.MouseEvent) => {
+ evt.preventDefault();
+ setLoading(true);
+
+ try {
+ const secret = await secretStore.load({ name, namespace });
+
+ try {
+ setSecretData(base64.decode(secret.data[key] ?? ""));
+ } catch {
+ setSecretData(secret.data[key]);
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ setSecretData(`${error}`);
+ } else if (isObject(error)) {
+ setSecretData(`Error: ${JSON.stringify(error)}`);
+ } else {
+ setSecretData(`Error: ${error}`);
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (secretData) {
+ return <>{secretData}>;
+ }
+
+ return (
+ <>
+ {`secret(${name})[${key}]`}
+
+
+ >
+ );
+};
+
+export const SecretKey = withInjectables(NonInjectedSecretKey, {
+ getProps: (di, props) => ({
+ ...props,
+ secretStore: di.inject(secretStoreInjectable),
+ }),
+});